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: Difference between revisions

From Path of Exile 2 Wiki
Jump to navigation Jump to search
>Illviljan
(Add alternate_art)
(gem_tier for gems)
(72 intermediate revisions by 10 users not shown)
Line 1: Line 1:
-- Item table
-------------------------------------------------------------------------------
--  
--  
-- Creates various item tables from cargo queries.
--                             Module:Item table
--  
--  
--  
-- This module implements Template:Item table and other templates that query
-- Todo list
-- and display tables or lists of items.
-- ---------
-------------------------------------------------------------------------------
-- * Handle table columns that can have multiple cargo rows, preferably
--   in one or two queries. Not per column AND row.
-- * Handle template include size? Remove item link when getting close
--   to the limit?
-- * Add a wrapper around f_item_link to be able to disable all
--   hoverboxes, to avoid running into template expansion size issues.


 
require('Module:No globals')
-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------
local m_util = require('Module:Util')
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 m_cargo = require('Module:Cargo')
local m_cargo = require('Module:Cargo')


-- ----------------------------------------------------------------------------
-- Should we use the sandbox version of our submodules?
-- Globals
local use_sandbox = m_util.misc.maybe_sandbox('Item table')
-- ----------------------------------------------------------------------------
 
local c = {}
c.query_default = 50
c.query_max = 300
 
-- ----------------------------------------------------------------------------
-- 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 = {
    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
local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or mw.loadData('Module:Game')
    item_table = {
        item = 'Item',
        skill_gem = 'Skill gem',


        physical_dps = m_util.html.abbr('pDPS', 'physical damage per second'),
-- Lazy loading
        fire_dps = m_util.html.abbr('Fire DPS', 'fire damage per second'),
local m_item_util -- require('Module:Item util')
        cold_dps = m_util.html.abbr('Cold DPS', 'cold damage per second'),
local f_item_link -- require('Module:Item link').item_link
        lightning_dps = m_util.html.abbr('Light. DPS', 'lightning damage per second'),
local f_skill_link -- require('Module:Skill link').skill_link
        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',
        metadata_id = 'Metadata ID',
        item_class = 'Item Class',
        essence_level = 'Essence<br>Level',
        drop_level = 'Drop<br>Level',
        release_version = 'Release<br>Version',
        removal_version = 'Removal<br>Version',
        version_link = '[[Version %s|%s]]',
        drop_enabled = m_util.html.abbr('Drop<br>Enabled', 'If an item is drop disabled, it can not be normally obtained, but still may be available under specific conditions (like trading via standard league or limited time events'),
        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'),
        master_level_requirement = '[[Image:Level up icon small.png‎|link=|Master level]]',
        master = 'Master',
        master_favour_cost = 'Favour<br>Cost',
        variation_count = 'Variations',
        buff_effects = 'Buff Effects',
        stats = 'Stats',
        quality_stats = 'Stats per 1% [[Quality]]',
        effects = 'Effect(s)',
        incubator_effect = 'Incubation Effect',
        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',
        quest_name = 'Quest',
        quest_act = 'Quest<br>Act',
        purchase_costs = m_util.html.abbr('Purchase Cost', 'Cost of purchasing an item of this type at NPC vendors. This does not indicate whether NPCs actually sell the item.'),
        sell_price = m_util.html.abbr('Sell Price', 'Items or currency received when selling this item at NPC vendors. Certain vendor recipes may override this value.'),
        sextant = m_util.html.abbr('Sextant', 'Maps within the sextant radius.'),
        boss_name = 'Boss',
        boss_number = 'Number of bosses',
        legacy = m_util.html.abbr('Legacy stats', 'Compare legacy variants to the current one.&#10;• Bright text indicates modifiers that are different from the latest variant.&#10;• Strike-through text indicates modifiers that do not exist on legacy variants.'),
        granted_skills = 'Granted skills',
        granted_skills_level_label = 'Level',
        granted_skills_level_pattern = '{granted_skills_level_label}%s*(%d+)',
        granted_skills_level_format = '{granted_skills_level_label} {level_number} ',
        granted_skills_skill_output_format = '{level}{img}[[{link}|{label}]]',
        granted_skills_gem_output_format = '{level}{il}',
        alternate_art = 'Alternate<br>Arts',
       
        -- 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'),
        attack_speed_multiplier = m_util.html.abbr('ASPD', 'Attack Speed Multiplier'),
        damage_effectiveness = m_util.html.abbr('Dmg.<br>Eff.', 'Effectiveness of Added Damage'),
        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'),
        vendor_rewards = m_util.html.abbr('Vendor rewards', 'Vendor rewards after quest completion'),
        vendor_rewards_row_format = '[[Act %s]] after [[%s]] from [[%s]] with %s.',
        vendor_rewards_any_classes = 'any character',
        quest_rewards = m_util.html.abbr('Quest rewards', 'Rewards after quest completion'),
        quest_rewards_row_format = '[[Act %s]] after [[%s]] with %s.',
        quest_rewards_any_classes = 'any character',
    },
   
    prophecy_description = {
        objective = 'Objective',
        reward = 'Reward',
    },
   
    item_disambiguation = {
        original='the original variant',
        drop_enabled='the current drop enabled variant',
        drop_disabled='a legacy variant',
        known_release = ' that was introduced in [[%s|%s]]',
        list_pattern='%s, %s%s.'
    },


    errors = {
-- The cfg table contains all localisable strings and configuration, to make it
        generic_argument_parameter = 'Unrecognized %s parameter "%s"',
-- easier to port this module to another wiki.
        invalid_item_table_mode = 'Invalid mode for item table',
local cfg = use_sandbox and mw.loadData('Module:Item table/config/sandbox') or mw.loadData('Module:Item table/config')
    },
}


local i18n = cfg.i18n


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- Helper & utility functions
-- Helper functions
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------


local h = {}
local h = {}
h.string = {}
function h.string.format(str, vars)
    --[[
    Allow string replacement using named arguments.
   
    TODO:
    * Support %d ?
    * Support 0.2f ?


function h.na_or_val(tr, value, func)
    Parameters
     if value == nil or value == '' then
     ----------
        tr:wikitext(m_util.html.td.na())
    str : String to replace.  
     else
    vars : Table of arguments.
        local raw_value = value
 
        if func ~= nil then
     Examples
            value = func(value)
    --------
        end
    = h.string.format('{foo} is {bar}.', {foo='Dinner', bar='nice'})
        tr
            :tag('td')
                :attr('data-sort-value', raw_value)
                :wikitext(value)
                :done()
    end
end


h.tbl = {}
    References
    ----------
    http://lua-users.org/wiki/StringInterpolation
    ]]


function h.tbl.range_fields(field)
     if not vars then
     return function()
         vars = str
         local fields = {}
         str = vars[1]
         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
   
    return (string.gsub(str, "({([^}]+)})",
        function(whole, i)
          return vars[i] or whole
        end))
end
end


h.tbl.display = {}
-- Lazy loading for Module:Item link
function h.tbl.display.na_or_val(tr, value, data)
function h.item_link(args)
     return h.na_or_val(tr, value)
    if not f_item_link then
        f_item_link = require('Module:Item link').item_link
    end
     return f_item_link(args)
end
end


function h.tbl.display.seconds(tr, value, data)
-- Lazy loading for Module:Skill link
     return h.na_or_val(tr, value, function(value)
function h.skill_link(args)
         return string.format('%ss', value)
     if not f_skill_link then
     end)
         f_skill_link = require('Module:Skill link').skill_link
     end
    return f_skill_link(args)
end
end


function h.tbl.display.percent(tr, value, data)
function h.range_fields_factory(args)
     return h.na_or_val(tr, value, function(value)
    -- Returns a function that gets the range fields for the given keys
        return string.format('%s%%', value)
    local suffixes = {'maximum', 'text', 'colour'}
     end)
    if args.full then
        suffixes[#suffixes+1] = 'minimum'
    end
     return function ()
        local fields = {}
        for _, field in ipairs(args.fields) do
            for _, suffix in ipairs(suffixes) do
                fields[#fields+1] = string.format('%s_range_%s', field, suffix)
            end
        end
        return fields
     end
end
end


function h.tbl.display.wikilink(tr, value, data)
function h.display_value_factory(args)
    return h.na_or_val(tr, value, function(value)
     args.fmt_options = args.fmt_options or {}
        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)
     return function(tr, data, fields, data2)
         local values = {}
         local values = {}
         local fmt_values = {}
         local fmt_values = {}
        local sdata = data2.skill_levels[data['items._pageName']]
       
         for index, field in ipairs(fields) do
         for index, field in ipairs(fields) do
             local value = {
             local value = {
Line 239: Line 111:
                 base=data[field],
                 base=data[field],
             }
             }
             if sdata then
            local sdata = data2 and data2.skill_levels[data['items._pageName']]
             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]
                 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]
                 value.max = value.max or sdata['0'][field] or sdata[data['skill.max_level']][field]
             end
             end
             if value.min then
             if value.min then
                 values[#values+1] = value.max
                 values[#values+1] = value.max
                 local opts = args.options[index] or {}
                 local options = args.fmt_options[index] or {}
                 -- global colour is set, no overrides
                 -- global color is set, no overrides
                 if args.colour ~= nil then
                 if args.color ~= nil then
                     opts.no_color = true
                     options.color = false
                 end
                 end
                 fmt_values[#fmt_values+1] = m_util.html.format_value(nil, nil, value, opts)
                 fmt_values[#fmt_values+1] = m_util.html.format_value(nil, value, options)
             end
             end
         end
         end
       
         if #values == 0 then
         if #values == 0 then
             tr:wikitext(m_util.html.td.na())
             tr:node(m_util.html.table_cell('na'))
         else
         else
             local td = tr:tag('td')
             local td = tr:tag('td')
             td:attr('data-sort-value', table.concat(values, ', '))
             td
            td:wikitext(table.concat(fmt_values, ', '))
                :attr('data-sort-value', table.concat(values, ', '))
             if args.colour then
                :wikitext(table.concat(fmt_values, ', '))
                 td:attr('class', 'tc -' .. args.colour)
             if args.color then
                 td:attr('class', 'tc -' .. args.color)
             end
             end
         end
         end
Line 267: Line 142:
end
end


function h.tbl.display.factory.range(args)
function h.display_range_factory(args)
     -- args: table
     -- args: table
     --  property
     --  property
Line 280: Line 155:
end
end


function h.tbl.display.factory.descriptor_value(args)
function h.display_range_composite_factory(args)
     -- Arguments:
     -- division by default
     --  key
     if args.func == nil then
    --  tbl
        args.func = function (a, b)
    args = args or {}
            if b == 0 then
    return function (tpl_args, frame, value)
                return 'fail'
        args.tbl = args.tbl or tpl_args
            end
        if args.tbl[args.key] then
             return a / b
             value = m_util.html.abbr(value, args.tbl[args.key])
         end
         end
        return value
     end
     end
end
   
 
    return function(tr, data, fields)
-- ----------------------------------------------------------------------------
        local field = {}
-- Data mappings
        for i=1, 2 do
-- ----------------------------------------------------------------------------
            local fieldn = args['field' .. i]
 
            field[i] = {
local data_map = {}
                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


-- for sort type see:
function h.display_descriptor_value_factory(args)
-- https://meta.wikimedia.org/wiki/Help:Sorting
    -- Arguments:
data_map.generic_item = {
    --  key
     {
    -- tbl
         arg = 'base_item',
    args = args or {}
        header = i18n.item_table.base_item,
     return function (value)
         fields = {'items.base_item', 'items.base_item_page'},
         if args.tbl[args.key] then
         display = function(tr, data)
            value = m_util.html.abbr(value, args.tbl[args.key])
             tr
        end
                :tag('td')
         return value
                    :attr('data-sort-value', data['items.base_item'])
    end
                    :wikitext(string.format('[[%s|%s]]', data['items.base_item_page'], data['items.base_item']))
end
         end,
 
        order = 1000,
function h.display_atlas_tier_factory(args)
        sort_type = 'text',
    args = args or {}
     },
    return function (tr, data)
     {
         for i=0,4 do
        arg = 'class',
            local t = tonumber(data['atlas_maps.map_tier' .. i])
         header = i18n.item_table.item_class,
 
         fields = {'items.class'},
             if t == 0 then
         display = h.tbl.display.factory.value{options = {
                tr
            [1] = {
                    :tag('td')
                fmt='[[%s]]',
                        :attr('table-sort-value', 0)
            },
                        :attr('class', 'table-cell-xmark')
        }},
                        :wikitext('✗')
         order = 1001,
            else
        sort_type = 'text',
                if args.is_level then
     },
                    t = t + 67
     {
                end
        arg = 'metadata_id',
                tr
         header = i18n.item_table.metadata_id,
                    :tag('td')
         fields = {'items.metadata_id'},
                        :attr('class', 'tc -value')
        display = h.tbl.display.factory.value{},
                        :wikitext(t)
        order = 1002,
                        :done()
     },
            end
    {
         end
        arg = 'essence',
    end
        header = i18n.item_table.essence_level,
end
        fields = {'essences.level'},
 
        display = h.tbl.display.factory.value{},
function h.display_yesno_factory(args)
        order = 2000,
    -- args:
    },
    --  field
     --  condition
     args = args or {}
    args.condition = args.condition or function (value)
         return m_util.cast.boolean(value, {cast_nil=false})
    end
    return function (tr, data)
         local type = args.condition(data[args.field]) and 'yes' or 'no'
         tr:node(m_util.html.table_cell(type))
    end
end
 
function h.value_greater_than_zero(value)
    value = m_util.cast.number(value, {default=0})
    if value > 0 then
         return true
    end
     return false
end
 
function h.na_or_val(tr, value)
     if value == nil or value == '' then
         tr:node(m_util.html.table_cell('na'))
    else
         tr
            :tag('td')
                :attr('data-sort-value', value)
                :wikitext(value)
     end
end
 
-- ----------------------------------------------------------------------------
-- Data mappings
-- ----------------------------------------------------------------------------
 
local data_map = {}
 
-- for sort type see:
-- https://meta.wikimedia.org/wiki/Help:Sorting
data_map.generic_item = {
     {
     {
         arg = {'drop', 'drop_level'},
         order = 1000,
         header = i18n.item_table.drop_level,
        args = {'base_item'},
         fields = {'items.drop_level'},
         header = i18n.item_table.base_item,
         display = h.tbl.display.factory.value{},
         fields = {'items.base_item', 'items.base_item_page'},
         order = 3000,
         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,
         sort_type = 'text',
     },
     {
     {
         arg = 'stack_size',
         order = 1001,
         header = i18n.item_table.stack_size,
        args = {'class'},
         fields = {'stackables.stack_size'},
         header = i18n.item_table.item_class,
         display = h.tbl.display.factory.value{},
         fields = {'items.class'},
         order = 4000,
         display = h.display_value_factory{
            fmt_options = {
                [1] = {
                    fmt = '[[%s]]',
                },
            },
        },
         sort_type = 'text',
     },
     },
     {
     {
         arg = 'stack_size_currency_tab',
         order = 1002,
         header = i18n.item_table.stack_size_currency_tab,
        args = {'rarity'},
         fields = {'stackables.stack_size_currency_tab'},
         header = i18n.item_table.rarity,
         display = h.tbl.display.factory.value{},
         fields = {'items.rarity'},
        order = 4001,
         display = h.display_value_factory{},
     },
     },
     {
     {
         arg = 'level',
         order = 1003,
         header = m_game.level_requirement.icon,
        args = {'rarity_id'},
         fields = h.tbl.range_fields('items.required_level'),
         header = i18n.item_table.rarity_id,
         display = h.tbl.display.factory.range{field='items.required_level'},
         fields = {'items.rarity_id'},
        order = 5000,
         display = h.display_value_factory{},
     },
     },
     {
     {
         arg = 'ar',
         order = 1004,
         header = i18n.item_table.armour,
        args = {'metadata_id'},
         fields = h.tbl.range_fields('armours.armour'),
         header = i18n.item_table.metadata_id,
         display = h.tbl.display.factory.range{field='armours.armour'},
         fields = {'items.metadata_id'},
        order = 6000,
         display = h.display_value_factory{},
     },
     },
     {
     {
        arg = 'ev',
         order = 1005,
        header =i18n.item_table.evasion,
         args = {'size'},
        fields = h.tbl.range_fields('armours.evasion'),
         header = i18n.item_table.inventory_size,
        display = h.tbl.display.factory.range{field='armours.evasion'},
         fields = {'items.size_x', 'items.size_y'},
        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)
         display = function (tr, data)
             tr
             tr
                 :tag('td')
                 :tag('td')
                     :attr('data-sort-value', data['weapons.damage_avg'])
                     :attr('data-sort-value', data['items.size_x'] * data['items.size_y'])
                     :wikitext(data['weapons.damage_html'])
                     :wikitext(string.format('%s×%s', data['items.size_x'], data['items.size_y']))
         end,
         end,
        order = 8000,
     },
     },
     {
     {
         arg = {'weapon', 'aps'},
         order = 1100,
         header = i18n.item_table.attacks_per_second,
        args = {'essence'},
         fields = h.tbl.range_fields('weapons.attack_speed'),
         header = i18n.item_table.essence_level,
         display = h.tbl.display.factory.range{field='weapons.attack_speed'},
         fields = {'essences.level'},
        order = 8001,
         display = h.display_value_factory{},
     },
     },
     {
     {
         arg = {'weapon', 'crit'},
         order = 1300,
         header = i18n.item_table.local_critical_strike_chance,
        args = {'stack_size'},
         fields = h.tbl.range_fields('weapons.critical_strike_chance'),
         header = i18n.item_table.stack_size,
         display = h.tbl.display.factory.range{field='weapons.critical_strike_chance'},
         fields = {'stackables.stack_size'},
        order = 8002,
         display = h.display_value_factory{},
     },
     },
     {
     {
         arg = {'physical_dps'},
         order = 1301,
         header = i18n.item_table.physical_dps,
        args = {'stack_size_currency_tab'},
         fields = h.tbl.range_fields('weapons.physical_dps'),
         header = i18n.item_table.stack_size_currency_tab,
         display = h.tbl.display.factory.range{field='weapons.physical_dps'},
         fields = {'stackables.stack_size_currency_tab'},
        order = 8100,
         display = h.display_value_factory{},
     },
     },
    -- Requirements
     {
     {
         arg = {'lightning_dps'},
         order = 1400,
         header = i18n.item_table.lightning_dps,
        args = {'level'},
         fields = h.tbl.range_fields('weapons.lightning_dps'),
         header = m_util.html.abbr(
         display = h.tbl.display.factory.range{field='weapons.lightning_dps'},
            string.format(
        order = 8101,
                '[[%s|link=|alt=%s]]',
                i18n.item_table.level_icon,
                i18n.item_table.required_level
            ),
            i18n.item_table.required_level
        ),
         fields = h.range_fields_factory{fields={'items.required_level'}},
         display = h.display_range_factory{field='items.required_level'},
     },
     },
     {
     {
         arg = {'cold_dps'},
         order = 1401,
         header = i18n.item_table.cold_dps,
        args = {'str'},
         fields = h.tbl.range_fields('weapons.cold_dps'),
         header = m_util.html.abbr(
         display = h.tbl.display.factory.range{field='weapons.cold_dps'},
            string.format(
        order = 8102,
                '[[%s|link=|alt=%s]]',
                i18n.item_table.str_icon,
                i18n.item_table.required_str
            ),
            i18n.item_table.required_str
        ),
         fields = h.range_fields_factory{fields={'items.required_strength'}},
         display = h.display_range_factory{field='items.required_strength'},
     },
     },
     {
     {
         arg = {'fire_dps'},
         order = 1402,
         header = i18n.item_table.fire_dps,
        args = {'dex'},
         fields = h.tbl.range_fields('weapons.fire_dps'),
         header = m_util.html.abbr(
         display = h.tbl.display.factory.range{field='weapons.fire_dps'},
            string.format(
        order = 8103,
                '[[%s|link=|alt=%s]]',
                i18n.item_table.dex_icon,
                i18n.item_table.required_dex
            ),
            i18n.item_table.required_dex
        ),
         fields = h.range_fields_factory{fields={'items.required_dexterity'}},
         display = h.display_range_factory{field='items.required_dexterity'},
     },
     },
     {
     {
         arg = {'chaos_dps'},
         order = 1403,
         header = i18n.item_table.chaos_dps,
        args = {'int'},
         fields = h.tbl.range_fields('weapons.chaos_dps'),
         header = m_util.html.abbr(
         display = h.tbl.display.factory.range{field='weapons.chaos_dps'},
            string.format(
        order = 8104,
                '[[%s|link=|alt=%s]]',
                i18n.item_table.int_icon,
                i18n.item_table.required_int
            ),
            i18n.item_table.required_int
        ),
         fields = h.range_fields_factory{fields={'items.required_intelligence'}},
         display = h.display_range_factory{field='items.required_intelligence'},
     },
     },
    -- Armour 15xx
     {
     {
         arg = {'elemental_dps'},
         order = 1500,
         header = i18n.item_table.elemental_dps,
        args = {'ar'},
         fields = h.tbl.range_fields('weapons.elemental_dps'),
         header = i18n.item_table.armour,
         display = h.tbl.display.factory.range{field='weapons.elemental_dps'},
         fields = h.range_fields_factory{fields={'armours.armour'}},
        order = 8105,
         display = h.display_range_factory{field='armours.armour'},
     },
     },
     {
     {
         arg = {'poison_dps'},
         order = 1501,
         header = i18n.item_table.poison_dps,
        args = {'ev'},
         fields = h.tbl.range_fields('weapons.poison_dps'),
         header =i18n.item_table.evasion,
         display = h.tbl.display.factory.range{field='weapons.poison_dps'},
         fields = h.range_fields_factory{fields={'armours.evasion'}},
        order = 8106,
         display = h.display_range_factory{field='armours.evasion'},
     },
     },
     {
     {
         arg = {'dps'},
         order = 1502,
         header = i18n.item_table.dps,
        args = {'es'},
         fields = h.tbl.range_fields('weapons.dps'),
         header = i18n.item_table.energy_shield,
         display = h.tbl.display.factory.range{field='weapons.dps'},
         fields = h.range_fields_factory{fields={'armours.energy_shield'}},
        order = 8107,
         display = h.display_range_factory{field='armours.energy_shield'},
     },
     },
     {
     {
         arg = 'flask_life',
         order = 1503,
         header = i18n.item_table.flask_life,
        args = {'wd'},
         fields = h.tbl.range_fields('flasks.life'),
         header = i18n.item_table.ward,
         display = h.tbl.display.factory.range{field='flasks.life'},
         fields = h.range_fields_factory{fields={'armours.ward'}},
        order = 9000,
         display = h.display_range_factory{field='armours.ward'},
     },
     },
     {
     {
         arg = 'flask_mana',
         order = 1504,
         header = i18n.item_table.flask_mana,
        args = {'block'},
         fields = h.tbl.range_fields('flasks.mana'),
         header = i18n.item_table.block,
         display = h.tbl.display.factory.range{field='flasks.mana'},
         fields = h.range_fields_factory{fields={'shields.block'}},
        order = 9001,
         display = h.display_range_factory{field='shields.block'},
     },
     },
    -- Weapons 16xx
     {
     {
         arg = 'flask',
         order = 1600,
         header = i18n.item_table.flask_duration,
        args = {'weapon', 'damage'},
         fields = h.tbl.range_fields('flasks.duration'),
         header = i18n.item_table.damage,
         display = h.tbl.display.factory.range{field='flasks.duration'},
         fields = {'weapons.damage_html', 'weapons.damage_avg'},
         order = 9002,
         display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['weapons.damage_avg'])
                    :wikitext(data['weapons.damage_html'])
         end,
     },
     },
     {
     {
         arg = 'flask',
         order = 1601,
         header = i18n.item_table.flask_charges_per_use,
        args = {'weapon', 'aps'},
         fields = h.tbl.range_fields('flasks.charges_per_use'),
         header = i18n.item_table.attacks_per_second,
         display = h.tbl.display.factory.range{field='flasks.charges_per_use'},
         fields = h.range_fields_factory{fields={'weapons.attack_speed'}},
        order = 9003,
         display = h.display_range_factory{field='weapons.attack_speed'},
     },
     },
     {
     {
         arg = 'flask',
         order = 1602,
         header = i18n.item_table.flask_maximum_charges,
        args = {'weapon', 'crit'},
         fields = h.tbl.range_fields('flasks.charges_max'),
         header = i18n.item_table.local_critical_strike_chance,
         display = h.tbl.display.factory.range{field='flasks.charges_max'},
         fields = h.range_fields_factory{fields={'weapons.critical_strike_chance'}},
        order = 9004,
         display = h.display_range_factory{field='weapons.critical_strike_chance'},
     },
     },
     {
     {
         arg = 'item_limit',
         order = 1603,
         header = i18n.item_table.item_limit,
        args = {'physical_dps'},
         fields = {'jewels.item_limit'},
         header = i18n.item_table.physical_dps,
         display = h.tbl.display.factory.value{},
         fields = h.range_fields_factory{fields={'weapons.physical_dps'}},
        order = 10000,
         display = h.display_range_factory{field='weapons.physical_dps'},
     },
     },
     {
     {
         arg = 'jewel_radius',
         order = 1604,
         header = i18n.item_table.jewel_radius,
        args = {'lightning_dps'},
         fields = {'jewels.radius_html'},
         header = i18n.item_table.lightning_dps,
         display = function (tr, data)
         fields = h.range_fields_factory{fields={'weapons.lightning_dps'}},
            tr
         display = h.display_range_factory{field='weapons.lightning_dps'},
                :tag('td')
                    :wikitext(data['jewels.radius_html'])
        end,
        order = 10001,
     },
     },
     {
     {
         arg = 'map_tier',
         order = 1605,
         header = i18n.item_table.map_tier,
        args = {'cold_dps'},
         fields = {'maps.tier'},
         header = i18n.item_table.cold_dps,
         display = h.tbl.display.factory.value{},
         fields = h.range_fields_factory{fields={'weapons.cold_dps'}},
        order = 11000,
         display = h.display_range_factory{field='weapons.cold_dps'},
     },
     },
     {
     {
         arg = 'map_level',
         order = 1606,
         header = i18n.item_table.map_level,
        args = {'fire_dps'},
         fields = {'maps.area_level'},
         header = i18n.item_table.fire_dps,
         display = h.tbl.display.factory.value{},
         fields = h.range_fields_factory{fields={'weapons.fire_dps'}},
        order = 11010,
         display = h.display_range_factory{field='weapons.fire_dps'},
     },
     },
     {
     {
         arg = 'map_guild_character',
         order = 1607,
         header = i18n.item_table.map_guild_character,
        args = {'chaos_dps'},
         fields = {'maps.guild_character'},
         header = i18n.item_table.chaos_dps,
         display = h.tbl.display.factory.value{},
         fields = h.range_fields_factory{fields={'weapons.chaos_dps'}},
        order = 11020,
         display = h.display_range_factory{field='weapons.chaos_dps'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = {'doodad', 'master_level_requirement'},
         order = 1608,
         header = i18n.item_table.master_level_requirement,
        args = {'elemental_dps'},
         fields = {'hideout_doodads.level_requirement'},
         header = i18n.item_table.elemental_dps,
         display = h.tbl.display.factory.value{},
         fields = h.range_fields_factory{fields={'weapons.elemental_dps'}},
        order = 11100,
         display = h.display_range_factory{field='weapons.elemental_dps'},
     },
     },
     {
     {
         arg = {'doodad', 'master'},
         order = 1609,
         header = i18n.item_table.master,
        args = {'poison_dps'},
         fields = {'hideout_doodads.master'},
         header = i18n.item_table.poison_dps,
         display = h.tbl.display.factory.value{},
         fields = h.range_fields_factory{fields={'weapons.poison_dps'}},
        order = 11101,
         display = h.display_range_factory{field='weapons.poison_dps'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = {'doodad', 'master_favour_cost'},
         order = 1610,
         header = i18n.item_table.master_favour_cost,
        args = {'dps'},
         fields = {'hideout_doodads.favour_cost'},
         header = i18n.item_table.dps,
         display = h.tbl.display.factory.value{colour='currency'},
         fields = h.range_fields_factory{fields={'weapons.dps'}},
        order = 11102,
         display = h.display_range_factory{field='weapons.dps'},
     },
     },
    -- Flasks 17xx
     {
     {
         arg = {'doodad', 'variation_count'},
         order = 1700,
         header = i18n.item_table.variation_count,
        args = {'flask_life'},
         fields = {'hideout_doodads.variation_count'},
         header = i18n.item_table.flask_life,
         display = h.tbl.display.factory.value{colour='mod'},
         fields = h.range_fields_factory{fields={'flasks.life'}},
        order = 11105,
         display = h.display_range_factory{field='flasks.life'},
     },
     },
     {
     {
         arg = 'buff',
         order = 1701,
         header = i18n.item_table.buff_effects,
        args = {'flask_life_per_second'},
         fields = {'item_buffs.stat_text'},
         header = i18n.item_table.flask_life_per_second,
         display = h.tbl.display.factory.value{colour='mod'},
         fields = h.range_fields_factory{fields={'flasks.life', 'flasks.duration'}, full=true},
        order = 12000,
         display = h.display_range_composite_factory{field1='flasks.life', field2='flasks.duration'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'stat',
         order = 1702,
         header = i18n.item_table.stats,
        args = {'flask_life_per_charge'},
         fields = {'items.stat_text'},
         header = i18n.item_table.flask_life_per_charge,
         display = h.tbl.display.factory.value{colour='mod'},
         fields = h.range_fields_factory{fields={'flasks.life', 'flasks.charges_per_use'}, full=true},
        order = 12001,
         display = h.display_range_composite_factory{field1='flasks.life', field2='flasks.charges_per_use'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'description',
         order = 1703,
         header = i18n.item_table.effects,
        args = {'flask_mana'},
         fields = {'items.description'},
         header = i18n.item_table.flask_mana,
         display = h.tbl.display.factory.value{colour='mod'},
         fields = h.range_fields_factory{fields={'flasks.mana'}},
        order = 12002,
         display = h.display_range_factory{field='flasks.mana'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'incubator_effect',
         order = 1704,
         header = i18n.item_table.incubator_effect,
        args = {'flask_mana_per_second'},
         fields = {'incubators.effect'},
         header = i18n.item_table.flask_mana_per_second,
         display = h.tbl.display.factory.value{colour='crafted'},
         fields = h.range_fields_factory{fields={'flasks.mana', 'flasks.duration'}, full=true},
        order = 12004,
         display = h.display_range_composite_factory{field1='flasks.mana', field2='flasks.duration'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'flavour_text',
         order = 1705,
         header = i18n.item_table.flavour_text,
        args = {'flask_mana_per_charge'},
         fields = {'items.flavour_text'},
         header = i18n.item_table.flask_mana_per_charge,
         display = h.tbl.display.factory.value{colour='flavour'},
         fields = h.range_fields_factory{fields={'flasks.mana', 'flasks.charges_per_use'}, full=true},
        order = 12006,
         display = h.display_range_composite_factory{field1='flasks.mana', field2='flasks.charges_per_use'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'help_text',
         order = 1706,
         header = i18n.item_table.help_text,
        args = {'flask'},
         fields = {'items.help_text'},
         header = i18n.item_table.flask_duration,
         display = h.tbl.display.factory.value{colour='help'},
         fields = h.range_fields_factory{fields={'flasks.duration'}},
        order = 12008,
         display = h.display_range_factory{field='flasks.duration'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = {'prophecy', 'objective'},
         order = 1707,
         header = i18n.item_table.objective,
        args = {'flask'},
         fields = {'prophecies.objective'},
         header = i18n.item_table.flask_charges_per_use,
        display = h.tbl.display.factory.value{},
         fields = h.range_fields_factory{fields={'flasks.charges_per_use'}},
        order = 13002,
         display = h.display_range_factory{field='flasks.charges_per_use'},
    },
        {
        arg = {'prophecy', 'reward'},
        header = i18n.item_table.reward,
        fields = {'prophecies.reward'},
         display = h.tbl.display.factory.value{},
        order = 13001,
     },
     },
     {
     {
         arg = {'prophecy', 'seal_cost'},
         order = 1708,
         header = i18n.item_table.seal_cost,
        args = {'flask'},
         fields = {'prophecies.seal_cost'},
         header = i18n.item_table.flask_maximum_charges,
         display = h.tbl.display.factory.value{colour='currency'},
         fields = h.range_fields_factory{fields={'flasks.charges_max'}},
        order = 13002,
         display = h.display_range_factory{field='flasks.charges_max'},
     },
     },
    -- Jewels 18xx
     {
     {
         arg = {'prediction_text'},
         order = 1800,
         header = i18n.item_table.prediction_text,
        args = {'jewel_limit'},
         fields = {'prophecies.prediction_text'},
         header = i18n.item_table.jewel_limit,
         display = h.tbl.display.factory.value{colour='value'},
         fields = {'jewels.jewel_limit'},
        order = 12004,
         display = h.display_value_factory{},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'buff_icon',
         order = 1801,
         header = i18n.item_table.buff_icon,
        args = {'jewel_radius'},
         fields = {'item_buffs.icon'},
         header = i18n.item_table.jewel_radius,
         display = h.tbl.display.factory.value{options = {
         fields = {'jewels.radius_html'},
             [1] = {
         display = function (tr, data)
                 fmt='[[%s]]',
             tr
            },
                 :tag('td')
         }},
                    :wikitext(data['jewels.radius_html'])
        order = 14000,
         end,
        sort_type = 'text',
     },
     },
    -- Maps 19xx
     {
     {
         arg = {'version', 'release_version'},
         order = 1900,
         header = i18n.item_table.release_version,
        args = {'map_tier'},
         fields = {'items.release_version'},
         header = i18n.item_table.map_tier,
         display = function(tr, data)
         fields = {'maps.tier'},
            tr
         display = h.display_value_factory{},
                :tag('td')
    },
                    :wikitext(
    {
                        string.format(
        order = 1901,
                            i18n.item_table.version_link,  
        args = {'map_level'},
                            data['items.release_version'],  
        header = i18n.item_table.map_level,
                            data['items.release_version']
        fields = {'maps.area_level'},
                        )
         display = h.display_value_factory{},
                    )
         end,
        order = 15000,
     },
     },
     {
     {
         arg = {'version', 'removal_version'},
         order = 1902,
         header = i18n.item_table.removal_version,
        args = {'map_guild_character'},
         fields = {'items.removal_version'},
         header = i18n.item_table.map_guild_character,
         display = function(tr, data)
         fields = {'maps.guild_character'},
            tr
         display = h.display_value_factory{},
                :tag('td')
        sort_type = 'text',
                    :wikitext(
                        string.format(
                            i18n.item_table.version_link,  
                            data['items.removal_version'],
                            data['items.removal_version']
                        )
                    )
        end,
        order = 15001,
     },
     },
     {
     {
         arg = {'drop', 'drop_enabled'},
         order = 1903,
         header = i18n.item_table.drop_enabled,
        args = {'map_series'},
         fields = {'items.drop_enabled'},
         header = i18n.item_table.map_series,
         display = h.tbl.display.factory.value{},
         fields = {'maps.series'},
        order = 15002,
         display = h.display_value_factory{},
     },
     },
     {
     {
         arg = {'drop', 'drop_leagues'},
         order = 1904,
         header = i18n.item_table.drop_leagues,
        args = {'atlas_tier'},
         fields = {'items.drop_leagues'},
         header = i18n.item_table.atlas_tier,
        display = function(tr, data)
         fields = {'atlas_maps.map_tier0', 'atlas_maps.map_tier1', 'atlas_maps.map_tier2', 'atlas_maps.map_tier3', 'atlas_maps.map_tier4'},
            tr
         display = h.display_atlas_tier_factory{is_level=false},
                :tag('td')
         colspan = 5,
                    :wikitext(table.concat(m_util.string.split(data['items.drop_leagues'], ','), '<br>'))
        end,
         order = 15003,
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'drop', 'drop_areas'},
         order = 1905,
         header = i18n.item_table.drop_areas,
        args = {'atlas_level'},
         fields = {'items.drop_areas_html'},
         header = i18n.item_table.atlas_level,
         display = h.tbl.display.factory.value{},
         fields = {'atlas_maps.map_tier0', 'atlas_maps.map_tier1', 'atlas_maps.map_tier2', 'atlas_maps.map_tier3', 'atlas_maps.map_tier4'},
         order = 15004,
         display = h.display_atlas_tier_factory{is_level=true},
        sort_type = 'text',
         colspan = 5,
     },
     },
    -- Map fragments 20xx
     {
     {
         arg = {'drop', 'drop_text'},
         order = 2000,
         header = i18n.item_table.drop_text,
        args = {'map_fragment', 'map_fragment_limit'},
         fields = {'items.drop_text'},
         header = i18n.item_table.map_fragment_limit,
         display = h.tbl.display.factory.value{},
         fields = {'map_fragments.map_fragment_limit'},
        order = 15005,
         display = h.display_value_factory{},
        sort_type = 'text',
     },
     },
    -- Hideout decorations 21xx
     {
     {
         arg = {'quest'},
         order = 2100,
         header = i18n.item_table.quest_rewards,
        args = {'doodad', 'variation_count'},
         fields = {'items._pageName'},
         header = i18n.item_table.variation_count,
         display = function(tr, data, na, results2)
         fields = {'hideout_doodads.variation_count'},
             if results2['quest_query'] == nil then
         display = h.display_value_factory{
                results2['quest_query'] = m_cargo.query(
             color = 'mod',
                    {'items', 'quest_rewards'},
        },
                    {
    },
                        'quest_rewards.reward',  
    -- Corpse items 22xx
                        'quest_rewards.classes',  
    {
                        'quest_rewards.act',
        order = 2200,
                        'quest_rewards.quest'
        args = {'corpse', 'monster_category'},
                    },
        header = i18n.item_table.monster_category,
                    {
        fields = {'corpse_items.monster_category', 'corpse_items.monster_category_html'},
                        join='items._pageName=quest_rewards.reward',
        display = function (tr, data)
                        where=string.format(
            tr
                            'items._pageID IN (%s) AND quest_rewards.reward IS NOT NULL',
                 :tag('td')
                            table.concat(results2.pageIDs, ', ')
                     :attr('data-sort-value', m_game.constants.monster.categories[data['corpse_items.monster_category']].id)
                        ),
                    :wikitext(data['corpse_items.monster_category_html'])
                        orderBy='quest_rewards.act, quest_rewards.quest',
        end,
                    }
    },
                 )
    {
                results2['quest_query'] = m_cargo.map_results_to_id{
        order = 2201,
                     results=results2['quest_query'],  
        args = {'corpse', 'monster_abilities'},
                    field='quest_rewards.reward',
        header = i18n.item_table.monster_abilities,
                }
        fields = {'corpse_items.monster_abilities'},
            end
        display = h.display_value_factory{
           
             color = 'mod',
            local results = results2['quest_query'][data['items._pageName']] or {}
         },
            local tbl = {}
         sort_type = 'text',
            for _, v in ipairs(results) do
                local classes = table.concat(m_util.string.split(v['quest_rewards.classes'] or '', '�'), ', ')
                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
           
            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',
     },
     },
    -- Seed data 91xx,
     {
     {
         arg = {'vendor'},
         order = 9100,
         header = i18n.item_table.vendor_rewards,
        args = {'seed', 'seed_type'},
         fields = {'items._pageName'},
         header = i18n.item_table.seed_type,
         display = function(tr, data, na, results2)
         fields = {'harvest_seeds.type', 'harvest_seeds.type_id'},
             if results2['vendor_query'] == nil then
         display = function (tr, data)
                results2['vendor_query'] = m_cargo.query(
             tr
                     {'items', 'vendor_rewards'},
                :tag('td')
                    {
                    :attr('table-sort-value', data['harvest_seeds.type'])
                        'vendor_rewards.reward',  
                    :attr('class', 'tc -' .. data['harvest_seeds.type_id'])
                        'vendor_rewards.classes',  
                     :wikitext(data['harvest_seeds.type'])
                        'vendor_rewards.act',  
        end,
                        'vendor_rewards.npc',  
    },
                        'vendor_rewards.quest',
    {
                    },
        order = 9101,
                    {
        args = {'seed', 'seed_tier'},
                        join='items._pageName=vendor_rewards.reward',
        header = i18n.item_table.seed_tier,
                        where=string.format(
        fields = {'harvest_seeds.tier'},
                            'items._pageID IN (%s) AND vendor_rewards.reward IS NOT NULL',  
        display = h.display_value_factory{},
                            table.concat(results2.pageIDs, ', ')
    },
                        ),
    {
                        orderBy='vendor_rewards.act, vendor_rewards.quest',
        order = 9102,
                    }
        args = {'seed', 'seed_growth_cycles'},
                )
        header = i18n.item_table.seed_growth_cycles,
                results2['vendor_query'] = m_cargo.map_results_to_id{
        fields = {'harvest_seeds.growth_cycles'},
                    results=results2['vendor_query'],  
        display = h.display_value_factory{},
                    field='vendor_rewards.reward',
    },
                }
    {
            end
        order = 9110,
            local results = results2['vendor_query'][data['items._pageName']] or {}
        args = {'seed', 'seed_cosumed_lifeforce', 'seed_consumed_lifeforce_percentage', 'seed_consumed_primal_lifeforce_percentage'},
           
        header = i18n.item_table.seed_consumed_primal_lifeforce_percentage,
             local tbl = {}
        fields = {'harvest_seeds.consumed_primal_lifeforce_percentage'},
            for _, v in ipairs(results) do
        display = h.display_value_factory{
                local classes = table.concat(m_util.string.split(v['vendor_rewards.classes'] or '', ''), ', ')
            color = 'primal',
                if classes == '' or classes == nil then
        },
                    classes = i18n.item_table.vendor_rewards_any_classes
    },
                end
    {
           
        order = 9110,
                tbl[#tbl+1] = string.format(
        args = {'seed', 'seed_cosumed_lifeforce', 'seed_consumed_lifeforce_percentage', 'seed_consumed_vivid_lifeforce_percentage'},
                    i18n.item_table.vendor_rewards_row_format,
        header = i18n.item_table.seed_consumed_vivid_lifeforce_percentage,
                    v['vendor_rewards.act'],  
        fields = {'harvest_seeds.consumed_vivid_lifeforce_percentage'},
                    v['vendor_rewards.quest'],  
        display = h.display_value_factory{
                    v['vendor_rewards.npc'],  
             color = 'vivid',
                    classes
        },
                )
    },
            end
    {
           
        order = 9110,
            value = table.concat(tbl, '<br>')
        args = {'seed', 'seed_cosumed_lifeforce', 'seed_consumed_lifeforce_percentage', 'seed_consumed_wild_lifeforce_percentage'},
            if value == nil or value == '' then
        header = i18n.item_table.seed_consumed_wild_lifeforce_percentage,
                tr:wikitext(m_util.html.td.na())
        fields = {'harvest_seeds.consumed_wild_lifeforce_percentage'},
            else
        display = h.display_value_factory{
                tr
            color = 'wild',
                    :tag('td')
        },
                        :attr('style', 'text-align:left')
    },
                        :wikitext(value)
    {
             end
        order = 9113,
        end,
        args = {'seed', 'seed_required_nearby_seeds', 'seed_required_nearby_seed_amount'},
         order = 17001,
        header = i18n.item_table.seed_required_nearby_seed_amount,
        fields = {'harvest_seeds.required_nearby_seed_amount'},
        display = h.display_value_factory{},
    },
    {
        order = 9114,
        args = {'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.display_value_factory{},
    },
    {
        order = 12000,
        args = {'buff'},
        header = i18n.item_table.buff_effects,
        fields = {'item_buffs.stat_text'},
        display = h.display_value_factory{
             color = 'mod',
         },
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'price', 'purchase_cost'},
         order = 12001,
         header = i18n.item_table.purchase_costs,
        args = {'stat'},
         fields = {'item_purchase_costs.name', 'item_purchase_costs.amount'},
         header = i18n.item_table.stats,
         display = function (tr, data)
         fields = {'items.stat_text'},
            -- Can purchase costs have multiple currencies and rows?
        display = h.display_value_factory{
            -- Just switch to the same method as in sell_price then.
            color = 'mod',
            local tbl = {}
        },
            if data['item_purchase_costs.name'] ~= nil then
         sort_type = 'text',
                tbl[#tbl+1] = string.format(
    },
                        '%sx %s',  
    {
                        data['item_purchase_costs.amount'],
        order = 12002,
                        f_item_link{data['item_purchase_costs.name']}
        args = {'description'},
                    )
        header = i18n.item_table.description,
            end
        fields = {'items.description'},
            h.na_or_val(tr, table.concat(tbl, '<br>'))
        display = h.display_value_factory{
         end,
            color = 'mod',
         order = 18001,
        },
        sort_type = 'text',
    },
    {
        order = 12003,
        args = {'seed', 'seed_effect'},
        header = i18n.item_table.seed_effects,
        fields = {'harvest_seeds.effect'},
         display = h.display_value_factory{
            color = 'crafted',
         },
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'price', 'sell_price'},
         order = 12100,
         header = i18n.item_table.sell_price,
        args = {'flavour_text'},
         fields = {'item_sell_prices.name', 'item_sell_prices.amount'},
         header = i18n.item_table.flavour_text,
         display = function(tr, data, na, results2)
         fields = {'items.flavour_text'},
            if results2['sell_price_query'] == nil then
         display = h.display_value_factory{
                results2['sell_price_query'] = m_cargo.query(
             color = 'flavour',
                    {'items', 'item_sell_prices'},
        },
                    {
         sort_type = 'text',
                        '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'],
                    f_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'},
         order = 12200,
         header = i18n.item_table.boss_name,
        args = {'help_text'},
         fields = {'maps.area_id'},  
         header = i18n.item_table.help_text,
         display = function(tr, data, na, results2)
         fields = {'items.help_text'},
            if results2['boss_query'] == nil then
         display = h.display_value_factory{
                results2['boss_query'] = m_cargo.query(
             color = 'help',
                    {'items', 'maps', 'areas'},
         },
                    {
                        'items._pageName',
                        'maps.area_id',
                        'areas.id',
                        'areas.boss_monster_ids'
                    },
                    {
                        join='items._pageID=maps._pageID, maps.area_id=areas.id',
                        -- where='maps.area_id IS NOT NULL AND areas.boss_monster_ids HOLDS LIKE "%"',
                        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, table.concat(tbl, '<br>'))
         end,
        order = 19001,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'boss', 'boss_number'},
         order = 12300,
         header = i18n.item_table.boss_number,
        args = {'buff_icon'},
         fields = {'maps.area_id'},  
         header = i18n.item_table.buff_icon,
         display = function(tr, data, na, results2)
         fields = {'item_buffs.icon'},
             if results2['boss_query'] == nil then
         display = h.display_value_factory{
                 results2['boss_query'] = m_cargo.query(
             fmt_options = {
                    {'items', 'maps', 'areas'},
                 [1] = {
                    {
                     fmt = '[[%s]]',
                        'items._pageName',
                 },
                        'maps.area_id',
             },
                        'areas.id',
         },
                        'areas.boss_monster_ids'
         sort_type = 'text',
                     },
                    {
                        join='items._pageID=maps._pageID, maps.area_id=areas.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']]
            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 = {'sextant'},
         order = 13000,
         header = i18n.item_table.sextant,
        args = {'prophecy', 'objective'},
         fields = {'items.name', 'atlas_maps.x', 'atlas_maps.y', 'maps.series'},  
         header = i18n.item_table.objective,
         display = function(tr, data, na, results2)
         fields = {'prophecies.objective'},
            if results2['sextant_query'] == nil then
         display = h.display_value_factory{},
                results2['sextant_query'] = m_cargo.query(
    },
                    {'items', 'maps', 'atlas_maps'},
    {
                    {
        order = 13001,
                        'items._pageName',
        args = {'prophecy', 'reward'},
                        'items.name',
        header = i18n.item_table.reward,
                        'maps.series',
        fields = {'prophecies.reward'},
                        'atlas_maps.x',
        display = h.display_value_factory{},
                        'atlas_maps.y',
                        'atlas_maps.connections'
                    },
                    {
                        join='items._pageID=atlas_maps._pageID, items._pageID=maps._pageID, maps._pageID=atlas_maps._pageID',
                        where='atlas_maps.x IS NOT NULL',
                        orderBy='maps.tier, items._pageName',
                    }
                )
            end
            local results = results2['sextant_query'] or {}
           
            local sextant_radius = 55 -- Should be in Module:Game?
            local x_center = data['atlas_maps.x']
            local y_center = data['atlas_maps.y']
           
            if not (x_center and y_center) then
                return
            end
           
            local tbl = {}
            for _, v in ipairs(results) do
                local x = v['atlas_maps.x']
                local y = v['atlas_maps.y']
                local r = ((x-x_center)^2 + (y-y_center)^2)^0.5
                if (sextant_radius >= r) and (data['items._pageName'] ~= v['items._pageName']) then -- 
                    tbl[#tbl+1] = f_item_link({page=v['items._pageName']})
                end
            end
            h.na_or_val(tr, table.concat(tbl, '<br>'))
        end,
        order = 20001,
        sort_type = 'text',
     },
     },
     {
     {
         arg = {'legacy'},
         order = 13002,
         header = i18n.item_table.legacy,
        args = {'prophecy', 'seal_cost'},
         fields = {'items.name'},  
         header = i18n.item_table.seal_cost,
         display = function(tr, data, na, results2)
         fields = {'prophecies.seal_cost'},
             if results2['legacy_query'] == nil then
         display = h.display_value_factory{
                results2['legacy_query'] = m_cargo.query(
             color = 'currency',
                    {'items', 'legacy_variants'},
        },
                    {
    },
                        'items._pageID',  
    {
                        'items._pageName',  
        order = 13003,
                        'items.frame_type',  
        args = {'prediction_text'},
                        'legacy_variants.removal_version',  
        header = i18n.item_table.prediction_text,
                        'legacy_variants.implicit_stat_text',
        fields = {'prophecies.prediction_text'},
                        'legacy_variants.explicit_stat_text',  
        display = h.display_value_factory{
                        'legacy_variants.stat_text',  
            color = 'value',
                        'legacy_variants.base_item',  
        },
                        'legacy_variants.required_level'
        sort_type = 'text',
                    },
    },
                    {
    {
                        join='items._pageID=legacy_variants._pageID',
        order = 14000,
                        where='legacy_variants.removal_version IS NOT NULL',
        args = {'version', 'release_version'},
                        where=string.format(
        header = i18n.item_table.release_version,
                            'items._pageID IN (%s) AND legacy_variants.removal_version IS NOT NULL',  
        fields = {'items.release_version'},
                            table.concat(results2.pageIDs, ', ')
        display = function(tr, data)
                        ),
            tr
                        orderBy='items._pageName',
                 :tag('td')
                    }
                    :wikitext(
                )
                        string.format(
               
                            i18n.item_table.version_link,
                 results2['legacy_query'] = m_cargo.map_results_to_id{
                            data['items.release_version'],
                    results=results2['legacy_query'],  
                            data['items.release_version']
                    field='items._pageName',
                        )
                }
                    )
            end
        end,
            local results = results2['legacy_query'][data['items._pageName']] or {}
    },
           
    {
            local tbl = mw.html.create('table')
        order = 15000,
                :attr('width', '100%')
        args = {'drop', 'drop_level'},
            for _, v in ipairs(results) do
        header = i18n.item_table.drop_level,
                local cell = {}
        fields = {'items.drop_level'},
                local l = {
        display = h.display_value_factory{},
                    'legacy_variants.base_item',  
    },
                    'legacy_variants.stat_text'
    {
                }
        order = 15001,
               
        args = {'drop_level_maximum'},
                -- Clean up data:
        header = i18n.item_table.drop_level_maximum,
                for _, k in ipairs(l) do
        fields = {'items.drop_level_maximum'},
                    if v[k] ~= nil then
        display = h.display_value_factory{},
                        local s = m_util.string.split(v[k], '*')
    },
                        local s_flt = {}
    {
                        for _, sss in ipairs(s) do
        order = 15002,
                            if sss ~= nil and sss ~= '' then
        args = {'version', 'removal_version'},
                                s_flt[#s_flt+1] = string.gsub(sss, '\n', '')
        header = i18n.item_table.removal_version,
                            end
        fields = {'items.removal_version'},
                        end
        display = function(tr, data)
                       
             tr
                        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')
                 :tag('td')
                     :node(tbl)
                     :wikitext(
                        string.format(
                            i18n.item_table.version_link,
                            data['items.removal_version'],
                            data['items.removal_version']
                        )
                    )
         end,
         end,
         order = 21001,
    },
    {
         order = 15003,
        args = {'drop', 'drop_enabled'},
        header = i18n.item_table.drop_enabled,
        fields = {'items.drop_enabled'},
        display = h.display_value_factory{},
        display = h.display_yesno_factory{
            field = 'items.drop_enabled',
        },
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'granted_skills'},
         order = 15004,
         header = i18n.item_table.granted_skills,
        args = {'drop', 'drop_areas'},
         fields = {'items.name'},  
         header = i18n.item_table.drop_areas,
         display = function(tr, data, na, results2)
         fields = {'items.drop_areas_html'},
             if results2['granted_skills_query'] == nil then  
        display = h.display_value_factory{},
                 results2['granted_skills_query'] = m_cargo.query(
        sort_type = 'text',
                     {'items', 'item_mods', 'mods', 'skill', 'items=items2'},
    },
    {
        order = 15005,
        args = {'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',  
                         'items._pageName',
                         'items.name',
                         'monsters._pageName',
                        'item_mods.id',
                         'monsters.name',
                        'mods._pageName',  
                         'main_pages._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',
                         join=[[
                         where=string.format(
                            items.drop_monsters HOLDS monsters.metadata_id,
                             'items._pageID IN (%s) AND mods.granted_skill IS NOT NULL',  
                            monsters.metadata_id=main_pages.id
                        ]],
                         where=string.format([[
                             items._pageID IN (%s)
                            AND monsters.metadata_id IS NOT NULL
                        ]],
                             table.concat(results2.pageIDs, ', ')
                             table.concat(results2.pageIDs, ', ')
                         ),
                         ),
                        orderBy='items.drop_monsters',
                     }
                     }
                 )
                 )
               
 
                 results2['granted_skills_query'] = m_cargo.map_results_to_id{
                 results2['drop_monsters_query'] = m_cargo.map_results_to_id{
                     results=results2['granted_skills_query'],  
                     results=results2['drop_monsters_query'],
                     field='items._pageName',
                     field='items._pageName',
                 }
                 }
             end  
             end
             local results = results2['granted_skills_query'][data['items._pageName']] or {}
             local results = results2['drop_monsters_query'][data['items._pageName']] or {}
           
             local tbl = {}
             local tbl = {}
             for _, v in ipairs(results) do
             for _,v in ipairs(results) do
           
                 local page = v['main_pages._pageName'] or v['monsters._pageName'] or ''
                 -- Check if a level for the skill is specified in the
                 local name = v['monsters.name'] or v['items.drop_monsters'] or ''
                -- mod stat text.
                 tbl[#tbl+1] = string.format('[[%s|%s]]', page, name)
                -- Stat ids have unreliable naming convention so using
            end
                -- the mod stat text instead.
            h.na_or_val(tr, table.concat(tbl, '<br>'))
                 local level = ''
        end,
                local stat_text = v['mods.stat_text_raw'] or ''
        sort_type = 'text',
                 local level_number = string.match(
    },
                    stat_text:lower(),  
    {
                    m_util.string.format(
        order = 15006,
                        i18n.item_table.granted_skills_level_pattern,
        args = {'drop', 'drop_text'},
                        {
        header = i18n.item_table.drop_text,
                            granted_skills_level_label = i18n.item_table.granted_skills_level_label:lower()
        fields = {'items.drop_text'},
                         }
        display = h.display_value_factory{},
                     )
        sort_type = 'text',
    },
    {
        order = 16000,
        args = {'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{
                -- If a level number was specified in the stat text
                     results=results2['quest_query'],
                -- then add it to the cell:
                    field='quest_rewards._pageName',
                if level_number then
                }
                    level = m_util.string.format(
            end
                        i18n.item_table.granted_skills_level_format,
 
                        {
            local results = results2['quest_query'][data['items._pageName']] or {}
                            granted_skills_level_label = i18n.item_table.granted_skills_level_label,
            local tbl = {}
                            level_number = level_number,
            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
                end
                     classes = i18n.item_table.quest_rewards_any_classes
               
                -- Use different formats depending on if it's a gem or
                -- not:
                if v['items2.class'] == nil  then
                     -- TODO: Better solution when there's a template for
                    -- skill links:
                    local img = ''
                    if v['skill.skill_icon'] ~= nil then
                        img = string.format(
                            '[[%s|16x16px|link=|alt=]]',  
                            v['skill.skill_icon']
                        )
                    end
                    local link = v['skill.active_skill_name'] or v['skill._pageName'] or v['mods._pageName'] or ''
                    local label = v['skill.active_skill_name'] or v['skill.stat_text'] or v['mods.granted_skill'] or ''
                    tbl[#tbl+1] = m_util.string.format(
                        i18n.item_table.granted_skills_skill_output_format,
                        {
                            level=level,
                            img=img,
                            link=link,
                            label=label,
                        }
                    )
                 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] = m_util.string.format(
                        i18n.item_table.granted_skills_gem_output_format,
                        {
                            level = level,
                            il = f_item_link(il_args),
                        }
                    )
                 end
                 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:node(m_util.html.table_cell('na'))
            else
                tr
                    :tag('td')
                        :attr('style', 'text-align:left')
                        :wikitext(value)
             end
             end
            h.na_or_val(tr, table.concat(tbl, '<br>'))
         end,
         end,
        order = 22001,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = 'alternate_art',
         order = 17000,
         header = i18n.item_table.alternate_art,
        args = {'vendor'},
         fields = {'items.alternate_art_inventory_icons'},
         header = i18n.item_table.vendor_rewards,
         display = function (tr, data)
         fields = {'items._pageName'},
             local alt_art = m_util.string.split(
         display = function(tr, data, na, results2)
                data['items.alternate_art_inventory_icons'],  
             if results2['vendor_query'] == nil then
                ','
                results2['vendor_query'] = m_cargo.query(
            )
                    {'items', 'vendor_rewards'},
           
                    {
            -- TODO: Use il instead to handle size?
                        'vendor_rewards._pageName',
            local size = 39
                        'vendor_rewards.classes',
            local out = {}
                        'vendor_rewards.act',
            for i,v in ipairs(alt_art) do
                        'vendor_rewards.npc',
                out[#out+1] = string.format(
                        'vendor_rewards.quest',
                    '[[%s|%spx|link=|%s]]',  
                    },
                    v,  
                    {
                    size,  
                        join='items._pageName=vendor_rewards._pageName',
                     v
                        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',
                     }
                 )
                 )
             end  
                results2['vendor_query'] = m_cargo.map_results_to_id{
              
                    results=results2['vendor_query'],
             tr
                    field='vendor_rewards._pageName',
                 :tag('td')
                }
                    :wikitext(table.concat(out, ''))
             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:node(m_util.html.table_cell('na'))
            else
                 tr
                    :tag('td')
                        :attr('style', 'text-align:left')
                        :wikitext(value)
            end
         end,
         end,
        order = 23000,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
}
data_map.skill_gem_new = {
     {
     {
         arg = 'icon',
         order = 18000,
         header = i18n.item_table.support_gem_letter,
         args = {'price', 'purchase_cost'},
        fields = {'skill_gems.support_gem_letter_html'},
         header = i18n.item_table.purchase_costs,
        display = h.tbl.display.factory.value{},
         fields = {'item_purchase_costs.name', 'item_purchase_costs.amount'},
        order = 1000,
         display = function (tr, data)
        sort_type = 'text',
            -- Can purchase costs have multiple currencies and rows?
    },
            -- Just switch to the same method as in sell_price then.
    {
            local tbl = {}
        arg = 'skill_icon',
             if data['item_purchase_costs.name'] ~= nil then
         header = i18n.item_table.skill_icon,
                 tbl[#tbl+1] = string.format(
         fields = {'skill.skill_icon'},
                        '%sx %s',
         display = h.tbl.display.factory.value{options = {
                        data['item_purchase_costs.amount'],
             [1] = {
                        h.item_link{data['item_purchase_costs.name']}
                 fmt='[[%s]]',
                    )
            },
            end
        }},
            h.na_or_val(tr, table.concat(tbl, '<br>'))
         order = 1001,
         end,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'stat', 'stat_text'},
         order = 18001,
         header = i18n.item_table.stats,
        args = {'price', 'sell_price'},
         fields = {'skill.stat_text'},
         header = i18n.item_table.sell_price,
         display = h.tbl.display.factory.value{},
         fields = {'item_sell_prices.name', 'item_sell_prices.amount'},
        order = 2000,
         display = function(tr, data, na, results2)
        sort_type = 'text',
            if results2['sell_price_query'] == nil then
    },
                results2['sell_price_query'] = m_cargo.query(
    {
                    {'items', 'item_sell_prices'},
        arg = {'quality', 'quality_stat_text'},
                    {
        header = i18n.item_table.quality_stats,
                        'item_sell_prices.name',
        fields = {'skill.quality_stat_text'},
                        'item_sell_prices.amount',
        display = h.tbl.display.factory.value{},
                        'item_sell_prices._pageID'
        order = 2001,
                    },
        sort_type = 'text',
                    {
    },
                        join='items._pageID=item_sell_prices._pageID',
    {
                        where=string.format(
        arg = 'description',
                            'items._pageID IN (%s) AND item_sell_prices._pageID IS NOT NULL',
        header = i18n.item_table.description,
                            table.concat(results2.pageIDs, ', ')
        fields = {'skill.description'},
                        ),
        display = h.tbl.display.factory.value{},
                        orderBy='item_sell_prices.name',
        order = 2100,
                    }
        sort_type = 'text',
                )
    },
                results2['sell_price_query'] = m_cargo.map_results_to_id{
    {
                    results=results2['sell_price_query'],
        arg = 'level',
                    field='item_sell_prices._pageID',
        header = m_game.level_requirement.icon,
                }
        fields = h.tbl.range_fields('items.required_level'),
            end
         display = h.tbl.display.factory.range{field='items.required_level'},
            local results = results2['sell_price_query'][data['items._pageID']] or {}
        order = 3004,
 
            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,
         sort_type = 'text',
     },
     },
     {
     {
         arg = 'crit',
         order = 19000,
         header = i18n.item_table.skill_critical_strike_chance,
        args = {'boss', 'boss_name'},
         fields = {'skill_levels.critical_strike_chance'},
         header = i18n.item_table.boss_name,
         display = h.tbl.display.factory.value{options = {
         fields = {'maps.area_id'},
            [1] = {
         display = function(tr, data, na, results2)
                fmt='%s%%',
            if results2['boss_query'] == nil then
                 skill_levels = true,
                results2['boss_query'] = m_cargo.query(
            },
                    {'items', 'maps', 'areas', 'monsters', 'main_pages'},
        }},
                    {
        order = 4000,
                        'items._pageName',
        options = {
                        'maps.area_id',
             [1] = {
                        'areas.id',
                 skill_levels = true,
                        '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,
         sort_type = 'text',
     },
     },
     {
     {
         arg = 'cast_time',
         order = 19001,
         header = i18n.item_table.cast_time,
        args = {'boss', 'boss_number'},
         fields = {'skill.cast_time'},
         header = i18n.item_table.boss_number,
         display = h.tbl.display.factory.value{options = {
         fields = {'maps.area_id'},
        }},
         display = function(tr, data, na, results2)
        order = 4001,
            if results2['boss_query'] == nil then
        options = {
                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
            tr
                :tag('td')
                    :attr('data-sort-value', #tbl)
                    :wikitext(#tbl)
         end,
     },
     },
     {
     {
         arg = {'aspd', 'attack_speed', 'attack_speed_multiplier'},
         order = 20000,
         header = i18n.item_table.attack_speed_multiplier,
        args = {'legacy'},
         fields = {'skill_levels.attack_speed_multiplier'},
         header = i18n.item_table.legacy,
         display = h.tbl.display.factory.value{options = {
         fields = {'items.name'},
             [1] = {
         display = function(tr, data, na, results2)
                 fmt='%s%%',
             if results2['legacy_query'] == nil then
                skill_levels = true,
                 results2['legacy_query'] = m_cargo.query(
            },
                    {'items', 'legacy_variants'},
        }},
                    {
        order = 4002,
                        'items._pageID',
        options = {
                        'items._pageName',
            [1] = {
                        'items.frame_type',
                skill_levels = true,
                        'legacy_variants.removal_version',
            },
                        'legacy_variants.implicit_stat_text',
        },
                        'legacy_variants.explicit_stat_text',
    },
                        'legacy_variants.stat_text',
    {
                        'legacy_variants.base_item',
        arg = 'dmgeff',
                        'legacy_variants.required_level'
        header = i18n.item_table.damage_effectiveness,
                    },
        fields = {'skill_levels.damage_effectiveness'},
                    {
        display = h.tbl.display.factory.value{options = {
                        join='items._pageID=legacy_variants._pageID',
            [1] = {
                        where='legacy_variants.removal_version IS NOT NULL',
                fmt='%s%%',
                        where=string.format(
                skill_levels = true,
                            'items._pageID IN (%s) AND legacy_variants.removal_version IS NOT NULL',
            },
                            table.concat(results2.pageIDs, ', ')
        }},
                        ),
        order = 4003,
                        orderBy='items._pageName',
        options = {
                    }
            [1] = {
                )
                skill_levels = true,
 
            },
                results2['legacy_query'] = m_cargo.map_results_to_id{
        },
                    results=results2['legacy_query'],
    },
                    field='items._pageName',
    {
                }
        arg = 'mcm',
            end
        header = i18n.item_table.mana_cost_multiplier,
            local results = results2['legacy_query'][data['items._pageName']] or {}
        fields = {'skill_levels.mana_multiplier'},
 
        display = h.tbl.display.factory.value{options = {
            local tbl = mw.html.create('table')
            [1] = {
                :attr('width', '100%')
                fmt='%s%%',
            for _, v in ipairs(results) do
                 skill_levels = true,
                local cell = {}
            },
                local l = {
        }},
                    'legacy_variants.base_item',
        order = 5000,
                    'legacy_variants.stat_text'
        options = {
                 }
            [1] = {
 
                skill_levels = true,
                -- 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 = {}
        arg = 'mana',
                        for _, sss in ipairs(s) do
        header = i18n.item_table.mana_cost,
                            if sss ~= nil and sss ~= '' then
        fields = {'skill_levels.mana_cost', 'skill.has_percentage_mana_cost', 'skill.has_reservation_mana_cost'},
                                s_flt[#s_flt+1] = string.gsub(sss, '\n', '')
        display = function (tr, data, fields, data2)
                            end
            local appendix = ''
                        end
            if m_util.cast.boolean(data['skill.has_percentage_mana_cost']) then
 
                 appendix = appendix .. '%%'
                        cell[#cell+1] = table.concat(s_flt, '<br>')
            end
                    end
            if m_util.cast.boolean(data['skill.has_reservation_mana_cost']) then
                end
                appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
 
                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
             end
              
             tr
            h.tbl.display.factory.value{options = {
                 :tag('td')
                 [1] = {
                     :node(tbl)
                    fmt='%d' .. appendix,
                     skill_levels = true,
                },
            }}(tr, data, {'skill_levels.mana_cost'}, data2)
         end,
         end,
         order = 5001,
         sort_type = 'text',
        options = {
            [1] = {
                skill_levels = true,
            },
        },
     },
     },
     {
     {
         arg = 'vaal',
         order = 21000,
         header = i18n.item_table.vaal_souls_requirement,
        args = {'granted_skills'},
         fields = {'skill_levels.vaal_souls_requirement'},
         header = i18n.item_table.granted_skills,
         display = h.tbl.display.factory.value{options = {
         fields = {'items.name'},
             [1] = {
         display = function(tr, data, na, results2)
                 skill_levels = true,
             if results2['granted_skills_query'] == nil then
            },
                 results2['granted_skills_query'] = m_cargo.query(
        }},
                    {'items', 'item_mods', 'mods', 'skill', 'items=items2'},
        order = 6000,
                    {
        options = {
                        'items._pageName',
            [1] = {
                        'items.name',
                skill_levels = true,
                        'item_mods.id',
            },
                        'mods._pageName',
        },
                        'mods.id',
    },
                        'mods.granted_skill',
    {
                        'mods.stat_text_raw',
        arg = 'vaal',
                        'skill._pageName',
        header = i18n.item_table.stored_uses,
                        'skill.skill_id',
        fields = {'skill_levels.vaal_stored_uses'},
                        'skill.active_skill_name',
        display = h.tbl.display.factory.value{options = {
                        'skill.skill_icon',
            [1] = {
                        'skill.stat_text',
                skill_levels = true,
                        'items2.class',
            },
                        'items2.name',
        }},
                        'items2.inventory_icon',
        order = 6001,
                        'items2.size_x',
        options = {
                        'items2.size_y',
            [1] = {
                        'items2.html',
                skill_levels = true,
                    },
            },
                     {
        },
                        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',
        arg = 'radius',
                            table.concat(results2.pageIDs, ', ')
        header = i18n.item_table.primary_radius,
                        ),
        fields = {'skill.radius', 'skill.radius_description'},
                    }
        options = {[2] = {optional = true}},
                )
        display = function (tr, data)
 
            tr
                results2['granted_skills_query'] = m_cargo.map_results_to_id{
                :tag('td')
                    results=results2['granted_skills_query'],
                    :attr('data-sort-value', data['skill.radius'])
                    field='items._pageName',
                     :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_description'}(nil, nil, data['skill.radius']))
                }
        end,
            end
        order = 7000,
            local results = results2['granted_skills_query'][data['items._pageName']] or {}
    },
 
    {
            local tbl = {}
        arg = 'radius',
            for _, v in ipairs(results) do
        header = i18n.item_table.secondary_radius,
 
        fields = {'skill.radius_secondary', 'skill.radius_secondary_description'},
                 -- Check if a level for the skill is specified in the
        options = {[2] = {optional = true}},
                -- mod stat text.
        display = function (tr, data)
                -- Stat ids have unreliable naming convention so using
            tr
                -- the mod stat text instead.
                 :tag('td')
                local level = ''
                    :attr('data-sort-value', data['skill.radius_secondary'])
                local stat_text = v['mods.stat_text_raw'] or ''
                    :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_secondary_description'}(nil, nil, data['skill.radius_secondary']))
                local level_number = string.match(
        end,
                    stat_text:lower(),
        order = 7001,
                    h.string.format(
    },
                        i18n.item_table.granted_skills_level_pattern,
    {
                        {
        arg = 'radius',
                            granted_skills_level_label = i18n.item_table.granted_skills_level_label:lower()
        header = i18n.item_table.tertiary_radius,
                        }
        fields = {'skill.radius_tertiary', 'skill.radius_tertiary_description'},
                    )
        options = {[2] = {optional = true}},
                 )
        display = function (tr, data)
 
            tr
                -- If a level number was specified in the stat text
                 :tag('td')
                -- then add it to the cell:
                    :attr('data-sort-value', data['skill.radius_tertiary'])
                if level_number then
                  :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_tertiary_description'}(nil, nil, data['skill.radius_tertiary']))
                    level = h.string.format(
        end,
                        i18n.item_table.granted_skills_level_format,
        order = 7002,
                        {
    },
                            granted_skills_level_label = i18n.item_table.granted_skills_level_label,
}
                            level_number = level_number,
                        }
                    )
                end


for i, attr in ipairs(m_game.constants.attribute_order) do
                -- Use different formats depending on if it's a gem or
    local attr_data = m_game.constants.attributes[attr]
                -- not:
    table.insert(data_map.generic_item, 7, {
                if v['items2.class'] == nil  then
        arg = attr_data.arg,
                    tbl[#tbl+1] = h.string.format(
        header = attr_data.icon,
                        i18n.item_table.granted_skills_skill_output_format,
        fields = h.tbl.range_fields(string.format('items.required_%s', attr)),
                        {
        display = h.tbl.display.factory.range{field=string.format('items.required_%s', attr)},
                            level = level,
        order = 5000+i,
                            sl = h.skill_link{
    })
                                skip_query=true,
    table.insert(data_map.skill_gem_new, 1, {
                                page = v['skill.active_skill_name']
        arg = attr_data.arg,
                                    or v['skill._pageName']
        header = attr_data.icon,
                                    or v['mods._pageName']
        fields = {string.format('skill_gems.%s_percent', attr)},
                                    or '',
        display = function (tr, data)
                                name = v['skill.active_skill_name']
            tr
                                    or v['skill.stat_text']
                :tag('td')
                                    or v['mods.granted_skill'],
                    :attr('data-sort-value', data[string.format('skill_gems.%s_percent', attr)])
                                icon = v['skill.skill_icon'],
                    :wikitext('[[File:Yes.png|yes|link=]]')
                            },
        end,
                        }
        order = 3000+i,
                    )
    })
                else
end
                    local il_args = {
 
                        skip_query=true,
 
                        page=v['items2._pageName'],
-- ----------------------------------------------------------------------------
                        name=v['items2.name'],
-- Invoke callables
                        inventory_icon=v['items2.inventory_icon'],
-- ----------------------------------------------------------------------------
                        width=v['items2.size_x'],
                        height=v['items2.size_y'],
                    }


local p = {}
                    -- TODO: add in tpl_args.
 
                    if no_html == nil then
--
                        il_args.html = v['items2.html']
-- Template:Item table
                    end
--
                    tbl[#tbl+1] = h.string.format(
 
                        i18n.item_table.granted_skills_gem_output_format,
function p.item_table(frame)
                        {
    --[[
                            level = level,
    Creates a generic table for items.
                            il = h.item_link(il_args),
   
                        }
     Examples
                    )
     --------
                end
    = p.item_table{
            end
         q_tables = 'items, vendor_rewards',
            h.na_or_val(tr, table.concat(tbl, '<br>'))
         q_join = 'items.name = vendor_rewards.reward',
        end,
         q_where= 'vendor_rewards.reward IS NOT NULL AND (items.class = "Active Skill Gems" OR items.class = "Support Skill Gems")',
        sort_type = 'text',
         vendor=1,
     },
    }
     {
   
        order = 23000,
    ]]
         args = {'alternate_art'},
   
         header = i18n.item_table.alternate_art,
    local t = os.clock()
         fields = {'items.alternate_art_inventory_icons'},
    -- args
         display = function (tr, data)
    local tpl_args = getArgs(frame, {
            local alt_art = m_util.string.split(
             parentFirst = true
                data['items.alternate_art_inventory_icons'],
        })
                ','
    frame = m_util.misc.get_frame(frame)
             )
   
    tpl_args.q_where = m_cargo.replace_holds{string=tpl_args.q_where}


     local modes = {
            -- TODO: Use il instead to handle size?
         skill = {
            -- local size = 39
            data = data_map.skill_gem_new,
            local out = {}
            header = i18n.item_table.skill_gem,
            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,
        sort_type = 'text',
    },
}
 
data_map.skill_gem = {
     {
        order = 1000,
        args = {'gem_tier'},
         header = i18n.item_table.tier,
        fields = {'skill_gems.gem_tier'},
        display = h.display_value_factory{},
    },
    {
        order = 1001,
        args = {'skill_icon'},
        header = i18n.item_table.skill_icon,
        fields = {'skill.skill_icon'},
        display = h.display_value_factory{
            fmt_options = {
                [1] = {
                    fmt = '[[%s]]',
                },
            },
         },
         },
         item = {
         sort_type = 'text',
            data = data_map.generic_item,
    },
            header = i18n.item_table.item,
     {
        },
        order = 2000,
     }
        args = {'stat', 'stat_text'},
   
        header = i18n.item_table.stats,
    if tpl_args.mode == nil then
         fields = {'skill.stat_text'},
         tpl_args.mode = 'item'
        display = h.display_value_factory{},
    end
        sort_type = 'text',
      
     },
     if modes[tpl_args.mode] == nil then
     {
         error(i18n.errors.invalid_item_table_mode)
         order = 2001,
    end
         args = {'quality', 'quality_stat_text'},
   
         header = i18n.item_table.quality_stats,
    local results2 = {
        fields = {'skill.quality_stat_text'},
         stats = {},
        display = h.display_value_factory{},
        skill_levels = {},
        sort_type = 'text',
         pageIDs = {},
     },
    }
     {
      
        order = 2100,
     local row_infos = {}
         args = {'description'},
    for _, row_info in ipairs(modes[tpl_args.mode].data) do
         header = i18n.item_table.description,
         local enabled = false
        fields = {'skill.description'},
         if row_info.arg == nil then
        display = h.display_value_factory{},
            enabled = true
         sort_type = 'text',
         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
        order = 3000,
            for _, argument in ipairs(row_info.arg) do
         args = {'level'},
                if m_util.cast.boolean(tpl_args[argument]) then
        header = m_util.html.abbr(
                    enabled = true
            string.format(
                    break
                 '[[%s|link=|alt=%s]]',
                 end
                i18n.item_table.level_icon,
            end
                i18n.item_table.gem_level_requirement
        end
            ),
       
             i18n.item_table.gem_level_requirement
        if enabled then
        ),
             row_info.options = row_info.options or {}
         fields = h.range_fields_factory{fields={'items.required_level'}},
            row_infos[#row_infos+1] = row_info
        display = h.display_range_factory{field='items.required_level'},
         end
    },
    end
     {
 
         order = 3001,
    -- Parse stat arguments
         args = {'str'},
    local stat_columns = {}
         header = m_util.html.abbr(
    local query_stats = {}
            string.format(
    local i = 0
                '[[%s|link=|alt=%s]]',
     repeat
                i18n.item_table.str_icon,
         i = i + 1
                i18n.item_table.str_gem
          
            ),
         local prefix = string.format('stat_column%s_', i)
             i18n.item_table.str_gem
        local col_info = {
        ),
            header = tpl_args[prefix .. 'header'] or tostring(i),
        fields = {'skill_gems.strength_percent'},
             format = tpl_args[prefix .. 'format'],
        display = h.display_yesno_factory{
            stat_format = tpl_args[prefix .. 'stat_format'] or 'separate',
            field = 'skill_gems.strength_percent',
             order = tonumber(tpl_args[prefix .. 'order']) or (10000000 + i),
             condition = h.value_greater_than_zero,
            stats = {},
        },
            options = {},
        sort_type = 'text',
         }
    },
          
    {
        local j = 0
        order = 3002,
        repeat
         args = {'dex'},
             j = j +1
         header = m_util.html.abbr(
       
             string.format(
            local stat_info = {
                '[[%s|link=|alt=%s]]',
                id = tpl_args[string.format('%sstat%s_id', prefix, j)],
                i18n.item_table.dex_icon,
            }
                i18n.item_table.dex_gem
              
             ),
             if stat_info.id then
             i18n.item_table.dex_gem
                col_info.stats[#col_info.stats+1] = stat_info
        ),
                query_stats[stat_info.id] = {}
        fields = {'skill_gems.dexterity_percent'},
             else
        display = h.display_yesno_factory{
                -- Stop iteration entirely if this was the first index but no stat was supplied. We assume that we stop in this case.
             field = 'skill_gems.dexterity_percent',
                if j == 1 then
            condition = h.value_greater_than_zero,
                    i = nil
        },
                end
        sort_type = 'text',
                -- stop iteration
    },
                j = nil
    {
            end
        order = 3003,
         until j == nil
         args = {'int'},
          
         header = m_util.html.abbr(
        -- Don't add this column if no stats were provided.  
            string.format(
        if #col_info.stats > 0 then
                '[[%s|link=|alt=%s]]',
            stat_columns[#stat_columns+1] = col_info
                i18n.item_table.int_icon,
        end
                i18n.item_table.int_gem
    until i == nil
            ),
 
             i18n.item_table.int_gem
    for _, col_info in ipairs(stat_columns) do
        ),
        local row_info = {
        fields = {'skill_gems.intelligence_percent'},
             --arg
        display = h.display_yesno_factory{
            header = col_info.header,
            field = 'skill_gems.intelligence_percent',
            fields = {},
            condition = h.value_greater_than_zero,
            display = function(tr, data, properties)
        },
                if col_info.stat_format == 'separate' then
        sort_type = 'text',
                    local stat_texts = {}
    },
                    local num_stats = 0
    {
                    local vmax = 0
        order = 4000,
                    for _, stat_info in ipairs(col_info.stats) do
        args = {'crit'},
                        num_stats = num_stats + 1
        header = i18n.item_table.skill_critical_strike_chance,
                        -- stat results from outside body
        fields = {'skill_levels.critical_strike_chance'},
                        local stat = (results2.stats[data['items._pageName']] or {})[stat_info.id]
        display = h.display_value_factory{
                        if stat ~= nil then
            fmt_options = {
                            stat_texts[#stat_texts+1] = m_util.html.format_value(tpl_args, frame, stat, {no_color=true})
                [1] = {
                            vmax = vmax + stat.max
                    fmt = '%s%%',
                        end
                },
                    end
            },
                   
        },
                    if num_stats ~= #stat_texts then
        field_options = {
                        tr:wikitext(m_util.html.td.na())
            [1] = {
                    else
                skill_levels = true,
                        local text
            },
                        if col_info.format then
        },
                            text = string.format(col_info.format, unpack(stat_texts))
    },
                        else
    {
                            text = table.concat(stat_texts, ', ')
        order = 4001,
                        end
        args = {'cast_time'},
                   
        header = i18n.item_table.cast_time,
                        tr:tag('td')
        fields = {'skill.cast_time'},
                            :attr('data-sort-value', vmax)
        display = h.display_value_factory{},
                            :attr('class', 'tc -mod')
    },
                            :wikitext(text)
    {
                    end
        order = 4002,
                elseif col_info.stat_format == 'add' then
        args = {'aspd', 'attack_speed', 'attack_speed_multiplier'},
                    local total_stat = {
        header = i18n.item_table.attack_speed_multiplier,
                        min = 0,
        fields = {'skill_levels.attack_speed_multiplier'},
                        max = 0,
        display = h.display_value_factory{
                        avg = 0,
            fmt_options = {
                    }
                [1] = {
                    for _, stat_info in ipairs(col_info.stats) do
                    fmt = '%s%%',
                        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]
        field_options = {
                            end
            [1] = {
                        end
                skill_levels = true,
                    end
            },
                   
        },
                    if col_info.format == nil then
    },
                        col_info.format = '%s'
    {
                    end
        order = 4003,
                   
        args = {'dmgeff'},
                     tr:tag('td')
        header = i18n.item_table.damage_effectiveness,
                        :attr('data-sort-value', total_stat.max)
        fields = {'skill_levels.damage_effectiveness'},
                        :attr('class', 'tc -mod')
        display = h.display_value_factory{
                        :wikitext(string.format(col_info.format, m_util.html.format_value(tpl_args, frame, total_stat, {no_color=true})))
            fmt_options = {
                else
                [1] = {
                    error(string.format(i18n.errors.generic_argument_parameter, 'stat_format', col_info.stat_format))
                     fmt = '%s%%',
                end
                },
            end,
            },
            order = col_info.order,
        },
         }
        field_options = {
         table.insert(row_infos, row_info)
            [1] = {
    end
                skill_levels = true,
          
            },
    -- sort the rows
        },
    table.sort(row_infos, function (a, b)
    },
        return (a.order or 0) < (b.order or 0)
    {
    end)
        order = 5000,
   
         args = {'mcm', 'cost_multiplier'},
    -- Parse query arguments
         header = i18n.item_table.cost_multiplier,
    local tables_assoc = {items=true}
        fields = {'skill_levels.cost_multiplier'},
    local fields = {
         display = h.display_value_factory{
        'items._pageID',
            fmt_options = {
         'items._pageName',
                [1] = {
         'items.name',
                    fmt = '%s%%',
         'items.inventory_icon',
                },
         'items.html',
            },
         'items.size_x',
        },
        'items.size_y',
        field_options = {
    }
            [1] = {
   
                skill_levels = true,
    --
            },
    local prepend = {
         },
        q_groupBy=true,
    },
        q_tables=true,
    {
    }
         order = 5001,
   
         args = {'mana'},
    local query = {}
         header = i18n.item_table.mana_cost,
    for key, value in pairs(tpl_args) do
         fields = {'skill_levels.cost_amounts', 'skill_levels.mana_reservation_percent', 'skill_levels.mana_reservation_flat'},
        if string.sub(key, 0, 2) == 'q_' then
        display = function (tr, data, fields, data2)
             if prepend[key] then
            local appendix = ''
                value = ',' .. value
            local cost_field = ''
            end
            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
            query[string.sub(key, 3)] = value
            -- Flat Mana resservation is in real gem levels, but maybe there will be fixed flat mana reservations in the future.
        end
            -- Per-use mana costs are stored in the real gem levels if they change, or in 0 if it's fixed.
    end
             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'
    local skill_levels = {}
                appendix = appendix .. '%%'
    for _, rowinfo in ipairs(row_infos) do
             elseif(sdata['1']['skill_levels.mana_reservation_flat'] ~= nil or sdata['0']['skill_levels.mana_reservation_flat'] ~= nil) then
        if type(rowinfo.fields) == 'function' then
                 cost_field = 'skill_levels.mana_reservation_flat'
             rowinfo.fields = rowinfo.fields()
                 appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
        end
            elseif(sdata['1']['skill_levels.cost_amounts'] ~= nil or sdata['0']['skill_levels.cost_amounts'] ~= nil) then
        for index, field in ipairs(rowinfo.fields) do
                cost_field = 'skill_levels.cost_amounts'
            rowinfo.options[index] = rowinfo.options[index] or {}
            end
            if rowinfo.options[index].skill_levels then
            h.display_value_factory{
                 skill_levels[#skill_levels+1] = field
                fmt_options = {
            else
                    [1] = {
                 fields[#fields+1] = field
                        fmt = '%d' .. appendix,
                tables_assoc[m_util.string.split(field, '%.')[1]] = true
                    },
            end
                },
        end
            }(tr, data, {cost_field}, data2)
    end
        end,
        -- Need one set of options per field.
        field_options = {
            [1] = {
                skill_levels = true,
            },
            [2] = {
                skill_levels = true,
            },
            [3] = {
                skill_levels = true,
            },
        },
    },
    {
        order = 6000,
        args = {'vaal'},
        header = i18n.item_table.vaal_souls_requirement,
        fields = {'skill_levels.vaal_souls_requirement'},
        display = h.display_value_factory{},
        field_options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        order = 6001,
        args = {'vaal'},
        header = i18n.item_table.stored_uses,
        fields = {'skill_levels.vaal_stored_uses'},
        display = h.display_value_factory{},
        field_options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        order = 7000,
        args = {'radius'},
        header = i18n.item_table.primary_radius,
        fields = {'skill.radius', 'skill.radius_description'},
        field_options = {
            [2] = {
                optional = true,
            },
        },
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['skill.radius'])
                    :wikitext(h.display_descriptor_value_factory{tbl=data, key='skill.radius_description'}(data['skill.radius']))
        end,
    },
    {
        order = 7001,
        args = {'radius'},
        header = i18n.item_table.secondary_radius,
        fields = {'skill.radius_secondary', 'skill.radius_secondary_description'},
        field_options = {
            [2] = {
                optional = true,
            },
        },
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['skill.radius_secondary'])
                    :wikitext(h.display_descriptor_value_factory{tbl=data, key='skill.radius_secondary_description'}(data['skill.radius_secondary']))
        end,
    },
    {
        order = 7002,
        args = {'radius'},
        header = i18n.item_table.tertiary_radius,
        fields = {'skill.radius_tertiary', 'skill.radius_tertiary_description'},
        field_options = {
            [2] = {
                optional = true,
            },
        },
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['skill.radius_tertiary'])
                  :wikitext(h.display_descriptor_value_factory{tbl=data, key='skill.radius_tertiary_description'}(data['skill.radius_tertiary']))
        end,
    },
}


    if #skill_levels > 0 then
-- ----------------------------------------------------------------------------
        fields[#fields+1] = 'skill.max_level'
-- Main functions
        tables_assoc.skill = true
-- ----------------------------------------------------------------------------
   
 
    end
local function _item_table(args)
   
     --[[
    -- Reformat the tables and fields so they can be retrieved correctly:   
     Creates a generic table for items.
    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 tpl_args.default ~= nil then
     Examples
         return tpl_args.default
    --------
     end
    = p.item_table{
      
        q_tables='vendor_rewards, quest_rewards, skill_gems',
     if #results > 0 then
        q_join='items._pageID=vendor_rewards._pageID, items._pageID=quest_rewards._pageID, items._pageID=skill_gems._pageID',
        -- Create a list of found pageIDs for column specific queries:
         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)',
        for _,v in ipairs(results) do
        vendor=1,
            results2.pageIDs[#results2.pageIDs+1] = v['items._pageID']
     }
         end
     ]]
       
 
         -- fetch skill level information
     m_item_util = m_item_util or require('Module:Item util')
         if #skill_levels > 0 then
 
             skill_levels[#skill_levels+1] = 'skill_levels._pageName'
    local t = os.clock()
             skill_levels[#skill_levels+1] = 'skill_levels.level'
 
            local pages = {}
    args.mode = args.mode or 'item'
            for _, row in ipairs(results) do
    local modes = {
                pages[#pages+1] = string.format('(skill_levels._pageID="%s" AND skill_levels.level IN (0, 1, %s))', row['items._pageID'], row['skill.max_level'])
         skill = {
            end
            data = data_map.skill_gem,
            local temp = m_cargo.query(
            header = i18n.item_table.skill_gem,
                {'skill_levels'},
         },
                skill_levels,
         item = {
                {
             data = data_map.generic_item,
                    where=table.concat(pages, ' OR '),
             header = i18n.item_table.item,
                    groupBy='skill_levels._pageID, skill_levels.level',
        },
                }
    }
            )
    if modes[args.mode] == nil then
            -- map to results
        error(i18n.errors.invalid_item_table_mode)
             for _, row in ipairs(temp) do
    end
                 if results2.skill_levels[row['skill_levels._pageName']] == nil then
 
                  results2.skill_levels[row['skill_levels._pageName']] = {}
    -- Handle deprecated "q_" args
                 end
    local query_params = {
                -- TODO: convert to int?
        'tables',
                results2.skill_levels[row['skill_levels._pageName']][row['skill_levels.level']] = row
        'join',
            end
        'where',
        'groupBy',
        'having',
        'orderBy',
        'limit',
        'offset',
    }
    for _, v in ipairs(query_params) do
        args[v] = args[v] or args['q_' .. v]
    end
 
    -- A where clause is required; there are far too many items to list in one table
    if args.where == nil then
        error(string.format(i18n.errors.generic_required_parameter, 'where'))
    end
 
    local results2 = {
        stats = {},
        skill_levels = {},
        pageIDs = {},
    }
 
    local row_infos = {}
    for _, row_info in ipairs(modes[args.mode].data) do
        local enabled = false
        if type(row_info.args) == 'table' then
             for _, a in ipairs(row_info.args) do
                 if m_util.cast.boolean(args[a]) then
                    enabled = true
                    break
                 end
            end
        else
            enabled = true
        end
        if enabled then
            row_info.field_options = row_info.field_options or {}
            row_infos[#row_infos+1] = row_info
         end
         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_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',
                }
            )


            for _, row in ipairs(temp) do
    -- Parse stat arguments
                local stat = {
    local stat_columns = {}
                    min = tonumber(row['item_stats.min']),
    local query_stats = {}
                    max = tonumber(row['item_stats.max']),
    for i=1, math.huge do -- repeat until no more columns are found
                    avg = tonumber(row['item_stats.avg']),
        local prefix = string.format('stat_column%s_', i)
                }
        if args[prefix .. 'stat1_id'] == nil then
               
            -- Each column requires at least one stat id
                if results2.stats[row['item_stats._pageName']] == nil then
            break
                    results2.stats[row['item_stats._pageName']] = {[row['item_stats.id']] = stat}
        end
                else
        local col_info = {
                    results2.stats[row['item_stats._pageName']][row['item_stats.id']] = stat
            header = args[prefix .. 'header'] or tostring(i),
                 end
            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
             end
            table.insert(col_info.stats, {id=stat_id})
            query_stats[stat_id] = true
         end
         end
        table.insert(stat_columns, col_info)
     end
     end
   
 
      
     for _, col_info in ipairs(stat_columns) do
    --
        local row_info = {
    -- Display the table
             header = col_info.header,
    --
             fields = {},
   
             display = function (tr, data)
    local tbl = mw.html.create('table')
                 if col_info.stat_format == 'separate' then
    tbl:attr('class', 'wikitable sortable item-table')
                    local stat_texts = {}
   
                    local num_stats = 0
    -- Headers:
                    local vmax = 0
    local tr = tbl:tag('tr')
                    for _, stat_info in ipairs(col_info.stats) do
    tr
                        num_stats = num_stats + 1
        :tag('th')
                        -- stat results from outside body
             :wikitext(modes[tpl_args.mode].header)
                        local stat = (results2.stats[data['items._pageName']] or {})[stat_info.id]
             :done()
                        if stat ~= nil then
              
                            stat_texts[#stat_texts+1] = m_util.html.format_value(args, stat, {color=false})
    for _, row_info in ipairs(row_infos) do
                            vmax = vmax + stat.max
        tr
                        end
            :tag('th')
                    end
                 :attr('data-sort-type', row_info.sort_type or 'number')
 
                :wikitext(row_info.header)
                    if num_stats ~= #stat_texts then
                :done()
                        tr:node(m_util.html.table_cell('na'))
    end
                    else
   
                        local text
    -- Rows:
                        if col_info.format then
    for _, row in ipairs(results) do
                            text = string.format(col_info.format, unpack(stat_texts))
        tr = tbl:tag('tr')
                        else
       
                            text = table.concat(stat_texts, ', ')
        local il_args = {
                        end
            skip_query=true,
 
            page=row['items._pageName'],
                        tr:tag('td')
            name=row['items.name'],
                            :attr('data-sort-value', vmax)
            inventory_icon=row['items.inventory_icon'],
                            :attr('class', 'tc -mod')
            width=row['items.size_x'],
                            :wikitext(text)
            height=row['items.size_y'],
                    end
        }
                elseif col_info.stat_format == 'add' then
       
                    local total_stat = {
        if tpl_args.no_html == nil then
                        min = 0,
            il_args.html = row['items.html']
                        max = 0,
        end
                        avg = 0,
       
                    }
        if tpl_args.large then
                    for _, stat_info in ipairs(col_info.stats) do
            il_args.large = tpl_args.large
                        local stat = (results2.stats[data['items._pageName']] or {})[stat_info.id]
        end
                        if stat ~= nil then
       
                            for k, v in pairs(total_stat) do
        tr
                                total_stat[k] = v + stat[k]
            :tag('td')
                            end
                :wikitext(f_item_link(il_args))
                         end
                :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
            end
            if display then
                rowinfo.display(tr, row, rowinfo.fields, results2)
            else
                tr:wikitext(m_util.html.td.na())
            end
        end
    end


    cats = {}
                    if col_info.format == nil then
    if #results == query.limit then
                        col_info.format = '%s'
        cats[#cats+1] = i18n.categories.query_limit
                    end
    end
 
   
                    tr:tag('td')
    if #results == 0 then
                        :attr('data-sort-value', total_stat.max)
         cats[#cats+1] = i18n.categories.no_results
                        :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
     end
   
    mw.logObject({os.clock() - t, query})
   
    return tostring(tbl) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
end


    -- sort the rows
    table.sort(row_infos, function (a, b)
        return (a.order or 0) < (b.order or 0)
    end)


-------------------------------------------------------------------------------
    -- Build Cargo query
-- Map item drops
    local tables = {'items'}
-------------------------------------------------------------------------------
    local fields = {
        'items._pageID',
        'items._pageName',
        'items.name',
        'items.inventory_icon',
        'items.html',
        'items.size_x',
        'items.size_y',
    }
    local query = {
        where = args.where,
        groupBy = table.concat({'items._pageID', args.groupBy}, ', '),
        having = args.having,
        orderBy = args.orderBy,
        limit = args.limit,
        offset = args.offset,
    }
 
    -- 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


function p.map_item_drops(frame)
     -- Minimum required tables and fields, based on display options
     --[[
     local skill_levels = {}
    Gets the area id from the map item and activates
     for _, rowinfo in ipairs(row_infos) do
    Template:Area_item_drops.
         if type(rowinfo.fields) == 'function' then
   
            rowinfo.fields = rowinfo.fields()
    Examples:
        end
     = p.map_item_drops{page='Underground River Map (War for the Atlas)'}
        for index, field in ipairs(rowinfo.fields) do
     ]]
            rowinfo.field_options[index] = rowinfo.field_options[index] or {}
   
            if rowinfo.field_options[index].skill_levels then
    -- Get args
                skill_levels[#skill_levels+1] = field
    local tpl_args = getArgs(frame, {
             else
         parentFirst = true
                fields[#fields+1] = field
    })
                tables[#tables+1] = m_util.string.split(field, '.', true)[1]
    frame = m_util.misc.get_frame(frame)
             end
   
         end
    tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
     end
   
     if #skill_levels > 0 then
    local results = m_cargo.query(
         fields[#fields+1] = 'skill.max_level'
        {'maps'},
        tables[#tables+1] = 'skill'
        {'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
     end
     return frame:expandTemplate{ title = 'Area item drops', args = {area_id=id} }
     tables = m_util.table.remove_duplicates(tables)
end


-------------------------------------------------------------------------------
    -- Minimum required joins, based on display options
-- Prophecy description
    local joins = {}
-------------------------------------------------------------------------------
    for _, table_name in ipairs(tables) do
        if table_name ~= 'items' then
            joins[#joins+1] = string.format('items._pageID=%s._pageID', table_name)
        end
    end


function p.prophecy_description(frame)
     -- Append additional tables
     -- Get args
     args.tables = m_util.cast.table(args.tables)
     local tpl_args = getArgs(frame, {
     if type(args.tables) == 'table' and #args.tables > 0 then
        parentFirst = true
         tables = m_util.table.merge(tables, args.tables)
    })
    frame = m_util.misc.get_frame(frame)
      
    tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
   
    local results = m_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
     end
      
 
     if results['prophecies.reward'] then
     -- Make join clause
         out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.reward)
     if #joins > 0 or args.join then
        out[#out+1] = results['prophecies.reward']
         -- m_util.table.merge rebuilds the table, which removes empty values
        query.join = table.concat(m_util.table.merge(joins, {args.join}), ', ')
    end
 
    -- Query results
    local results = m_cargo.query(tables, fields, query)
 
    if #results == 0 and args.default ~= nil then
        return args.default
     end
     end
   
    return table.concat(out, '\n')
end


-- ----------------------------------------------------------------------------
    if #results > 0 then
-- Item disambiguation
         -- Create a list of found pageIDs for column specific queries:
-- ----------------------------------------------------------------------------
         for _,v in ipairs(results) do
function h.find_aliases(tpl_args)
             results2.pageIDs[#results2.pageIDs+1] = v['items._pageID']
  --[[
  This function queries items for an item name, then checks if it has
  had any name changes then queries for that name as well.
  ]]
   
    -- Get initial name:
    tpl_args.name_list = {
         tpl_args.name or m_util.string.split(
            tostring(mw.title.getCurrentTitle()),
            ' %('
        )
    }
   
    -- Query for items with similar name, repeat until no new names are
    -- found.
    local n
    local results = {}
    local hash = {}
    repeat
        local n_old = #tpl_args.name_list
       
        -- Multiple HOLDS doesn't work. Using __FULL and REGEXP instead.
        local where_tbl = {}
         for _, item_name in ipairs(tpl_args.name_list) do
             for _, prefix in ipairs({'', 'Shaped '}) do
                where_tbl[#where_tbl+1] = string.format(
                    '(items.name_list__FULL REGEXP "(�|^)%s%s(�|$)")',
                    prefix,
                    item_name
                )
            end
         end
         end
        local where_str = table.concat(where_tbl, ' OR ')
 
          
         -- fetch skill level information
         results = m_cargo.query(
         if #skill_levels > 0 then
             {'items', 'maps'},
             skill_levels[#skill_levels+1] = 'skill_levels._pageName'
            {
             skill_levels[#skill_levels+1] = 'skill_levels.level'
                'items._pageName',
            local pages = {}
                'items.name',
             for _, row in ipairs(results) do
                'items.name_list',
                pages[#pages+1] = string.format('(skill_levels._pageID="%s" AND skill_levels.level IN (0, 1, %s))', row['items._pageID'], row['skill.max_level'])
                'items.release_version',
                'items.removal_version',
                'items.drop_enabled',
             },
            {
                join='items._pageName=maps._pageName',
                where=where_str,
                groupBy='items._pageName',
                orderBy='items.release_version DESC, items.removal_version DESC, items.name ASC, maps.area_id ASC',
             }
        )
       
        -- Filter duplicates:
        for i,v in ipairs(results) do
            local r = m_util.string.split(v['items.name_list'], '')
            if type(r) == string then
                r = {r}
             end
             end
              
             local temp = m_cargo.query(
             for j,m in ipairs(r) do
                {'skill_levels'},
                 if hash[m] == nil then
                skill_levels,
                    hash[m] = m
                {
                    tpl_args.name_list[#tpl_args.name_list+1] = m
                    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
                 end
                -- TODO: convert to int?
                results2.skill_levels[row['skill_levels._pageName']][row['skill_levels.level']] = row
             end
             end
         end
         end
    until #tpl_args.name_list == n_old
   
    return results
end


function p.item_disambiguation(frame)
        if #stat_columns > 0 then
    --[[
            local stat_results = m_cargo.query(
    This function finds that items with a name or has had that name.
                {'items', 'item_stats'},
   
                {'item_stats._pageName', 'item_stats.id', 'item_stats.min', 'item_stats.max', 'item_stats.avg'},
    To do
                {
    -----
                    join = 'items._pageID=item_stats._pageID',
    Should text imply which is the original map, even if it isn't (Original)?
                    where = string.format(
    How to properly sort drop disabled items, with removal version?
                        'item_stats._pageID IN (%s) AND item_stats.id IN ("%s")',
    How to deal with names that have been used multiple times? Terrace Map
                        table.concat(m_util.table.column(results, 'items._pageID'), ','),
   
                        table.concat(m_util.table.keys(query_stats), '","')
    Examples
                    ),
    --------
                    groupBy = 'items._pageID, item_stats.id',
    = p.item_disambiguation{name='Abyss Map'}
                }
    = p.item_disambiguation{name='Caldera Map'}
            )
    = p.item_disambiguation{name='Crypt Map'}
            for _, row in ipairs(stat_results) do
    = p.item_disambiguation{name='Catacombs Map'}
                local stat = {
    = p.item_disambiguation{name='Harbinger Map (High Tier)'}
                    min = tonumber(row['item_stats.min']),
    ]]
                    max = tonumber(row['item_stats.max']),
   
                    avg = tonumber(row['item_stats.avg']),
    -- Get template arguments.
                }
    local tpl_args = getArgs(frame, {
 
        parentFirst = true
                if results2.stats[row['item_stats._pageName']] == nil then
    })
                     results2.stats[row['item_stats._pageName']] = {[row['item_stats.id']] = stat}
    frame = m_util.misc.get_frame(frame)
                 else
   
                    results2.stats[row['item_stats._pageName']][row['item_stats.id']] = stat
    local current_title = tostring(mw.title.getCurrentTitle())
                 end
    -- Get the page name.
    tpl_args.name = tpl_args.name or m_util.string.split(
        current_title,  
        ' %('
    )[1]
   
    -- Query for items with similar name.
    local results = h.find_aliases(tpl_args)
   
    -- Format the results:
    local out = {}
    local container = mw.html.create('div')
    local tbl = container:tag('ul')
    for i,v in ipairs(results) do
        if v['items._pageName'] ~= current_title then
            -- Get the content inside the last parentheses:
            local known_release = string.reverse(
                string.match(
                     string.reverse(v['items._pageName']),
                    '%)(.-)%('
                )
            )
       
            local drop_enabled
            if known_release == 'Original' then
                 drop_enabled = i18n.item_disambiguation.original
            elseif m_util.cast.boolean(v['items.drop_enabled']) then
                drop_enabled = i18n.item_disambiguation.drop_enabled
            else
                 drop_enabled = i18n.item_disambiguation.drop_disabled
             end
             end
           
         end
           
            if known_release ~= 'Original' then
                known_release = string.format(
                    i18n.item_disambiguation.known_release,
                    known_release,
                    known_release
                )
            else
                known_release = ''
            end
           
            tbl
                :tag('li')
                    :wikitext(
                        string.format(
                            i18n.item_disambiguation.list_pattern,
                            f_item_link{page=v['items._pageName']},
                            drop_enabled,
                            known_release
                        )
                    )
         end
     end
     end
     out[#out+1] = tostring(container)
 
      
     --
     -- Add a category when the template uses old template inputs:
     -- Display the table
     local old_args = {
     --
        'war',
 
        'atlas',
     local tbl = mw.html.create('table')
        'awakening',
    tbl:attr('class', 'wikitable sortable item-table')
        'original',
     if m_util.cast.boolean(args.responsive) then
        'heading',
        tbl:addClass('responsive-table')
        'hide_heading',
        'show_current',
    }
     for _,v in ipairs(old_args) do
        if tpl_args[v] ~= nil then
            return table.concat(out, '') .. m_util.misc.add_category(
                {'Pages with old template arguments'}
            )
        end
     end
     end
   
    return table.concat(out, '')
end


function p.simple_item_list(frame)
     -- Headers:
     --[[
     local tr = tbl:tag('tr')
    Creates a simple list of items.
    tr
   
         :tag('th')
    Examples
            :wikitext(modes[args.mode].header)
    --------
            :done()
     = p.simple_item_list{
     for _, row_info in ipairs(row_infos) do
        q_tables='maps',
         local th = tr:tag('th')
         q_join='items._pageID=maps._pageID',
 
        q_where='maps.tier=1 AND items.drop_enabled=1 AND items.rarity_id="normal"',
        if row_info.colspan then
        no_html=1,
             th:attr('colspan', row_info.colspan)
        link_from_name=1,
    }
    ]]
   
    -- Args
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
   
    local query = {}
     for key, value in pairs(tpl_args) do  
         if string.sub(key, 0, 2) == 'q_' then
             query[string.sub(key, 3)] = value
         end
         end
        th
            :attr('data-sort-type', row_info.sort_type or 'number')
            :wikitext(row_info.header)
     end
     end
   
 
    local fields = {
     -- Rows:
        'items._pageName',
        'items.name',
        'items.class',
    }
   
    if tpl_args.no_icon == nil then
        fields[#fields+1] = 'items.inventory_icon'
    end
   
    if tpl_args.no_html == nil then
        fields[#fields+1] = 'items.html'
    end
   
    local tables = m_util.string.split(tpl_args.q_tables or '', ',%s*')
    table.insert(tables, 'items')
   
    query.groupBy = query.groupBy or 'items._pageID'
   
    local results = m_cargo.query(
        tables,
        fields,
        query
    )
       
     local out = {}
     for _, row in ipairs(results) do
     for _, row in ipairs(results) do
         local page
         tr = tbl:tag('tr')
         if tpl_args.use_name_as_link ~= nil then
 
             page = row['items.name']
         local il_args = {
        else
             skip_query=true,
             page = row['items._pageName']
             page=row['items._pageName'],
        end
             name=row['items.name'],
       
             inventory_icon=row['items.inventory_icon'],
        local link = f_item_link{
             width=row['items.size_x'],
            page=page,  
             height=row['items.size_y'],
             name=row['items.name'],  
             inventory_icon=row['items.inventory_icon'] or '',  
             html=row['items.html'] or '',  
             skip_query=true
         }
         }
       
 
         if tpl_args.format == nil then
         if args.no_html == nil then
             out[#out+1] = string.format('* %s', link)
             il_args.html = row['items.html']
         elseif tpl_args.format == 'none' then
         end
            out[#out+1] = link
 
         elseif tpl_args.format == 'li' then
         if args.large then
             out[#out+1] = string.format('<li>%s</li>', link)
             il_args.large = args.large
        else
            error(string.format(i18n.errors.generic_argument_parameter, 'format', tpl_args.format))
         end
         end
     end
 
      
        tr
     if tpl_args.format == nil then
            :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 options = rowinfo.field_options[index]
                    if options.optional ~= true and options.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:node(m_util.html.table_cell('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, {tables=tables, fields=fields, query=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')
         return table.concat(out, '\n')
     elseif tpl_args.format == 'none' then
     elseif args.format == 'li' then
         return table.concat(out, '\n')
         return table.concat(out)
     elseif tpl_args.format == 'li' then
    end
         return table.concat(out)
end
 
-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------
 
local p = {}
 
--
-- Template:Item table
--
p.item_table = m_util.misc.invoker_factory(_item_table)
 
--
-- Template:Map item drops
--
p.map_item_drops = m_util.misc.invoker_factory(_map_item_drops, {
    wrappers = cfg.wrappers.map_item_drops,
})
 
--
-- Template:Prophecy description
--
p.prophecy_description = m_util.misc.invoker_factory(_prophecy_description, {
    wrappers = cfg.wrappers.prophecy_description,
})
 
--
-- Template:Simple item list
--
p.simple_item_list = m_util.misc.invoker_factory(_simple_item_list, {
    wrappers = cfg.wrappers.simple_item_list,
})
 
-- ----------------------------------------------------------------------------
-- 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
     end
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, ', ')
     return table.concat(out, ', ')
end
end
Line 2,386: Line 2,488:


function p.debug.skill_gem_all()
function p.debug.skill_gem_all()
     return p.debug._tbl_data(data_map.skill_gem_new)
     return p.debug._tbl_data(data_map.skill_gem)
end
end
-- ----------------------------------------------------------------------------
-- Return
-- ----------------------------------------------------------------------------


return p
return p

Revision as of 15:22, 14 January 2025

Module documentation[view] [edit] [history] [purge]


The item module provides functionality for creating item tables.

Implemented templates

This module implements the following templates:

-------------------------------------------------------------------------------
-- 
--                             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.range_fields_factory(args)
    -- Returns a function that gets the range fields for the given keys
    local suffixes = {'maximum', 'text', 'colour'}
    if args.full then
        suffixes[#suffixes+1] = 'minimum'
    end
    return function ()
        local fields = {}
        for _, field in ipairs(args.fields) do
            for _, suffix in ipairs(suffixes) do
                fields[#fields+1] = string.format('%s_range_%s', field, suffix)
            end
        end
        return fields
    end
end

function h.display_value_factory(args)
    args.fmt_options = args.fmt_options or {}
    return function(tr, data, fields, data2)
        local values = {}
        local fmt_values = {}
        for index, field in ipairs(fields) do
            local value = {
                min=data[field],
                max=data[field],
                base=data[field],
            }
            local sdata = data2 and data2.skill_levels[data['items._pageName']]
            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 options = args.fmt_options[index] or {}
                -- global color is set, no overrides
                if args.color ~= nil then
                    options.color = false
                end
                fmt_values[#fmt_values+1] = m_util.html.format_value(nil, value, options)
            end
        end
        if #values == 0 then
            tr:node(m_util.html.table_cell('na'))
        else
            local td = tr:tag('td')
            td
                :attr('data-sort-value', table.concat(values, ', '))
                :wikitext(table.concat(fmt_values, ', '))
            if args.color then
                td:attr('class', 'tc -' .. args.color)
            end
        end
    end
end

function h.display_range_factory(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.display_range_composite_factory(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.display_descriptor_value_factory(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.display_atlas_tier_factory(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

function h.display_yesno_factory(args)
    -- args:
    --  field
    --  condition
    args = args or {}
    args.condition = args.condition or function (value)
        return m_util.cast.boolean(value, {cast_nil=false})
    end
    return function (tr, data)
        local type = args.condition(data[args.field]) and 'yes' or 'no'
        tr:node(m_util.html.table_cell(type))
    end
end

function h.value_greater_than_zero(value)
    value = m_util.cast.number(value, {default=0})
    if value > 0 then
        return true
    end
    return false
end

function h.na_or_val(tr, value)
    if value == nil or value == '' then
        tr:node(m_util.html.table_cell('na'))
    else
        tr
            :tag('td')
                :attr('data-sort-value', value)
                :wikitext(value)
    end
end

-- ----------------------------------------------------------------------------
-- Data mappings
-- ----------------------------------------------------------------------------

local data_map = {}

-- for sort type see:
-- https://meta.wikimedia.org/wiki/Help:Sorting
data_map.generic_item = {
    {
        order = 1000,
        args = {'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,
        sort_type = 'text',
    },
    {
        order = 1001,
        args = {'class'},
        header = i18n.item_table.item_class,
        fields = {'items.class'},
        display = h.display_value_factory{
            fmt_options = {
                [1] = {
                    fmt = '[[%s]]',
                },
            },
        },
        sort_type = 'text',
    },
    {
        order = 1002,
        args = {'rarity'},
        header = i18n.item_table.rarity,
        fields = {'items.rarity'},
        display = h.display_value_factory{},
    },
    {
        order = 1003,
        args = {'rarity_id'},
        header = i18n.item_table.rarity_id,
        fields = {'items.rarity_id'},
        display = h.display_value_factory{},
    },
    {
        order = 1004,
        args = {'metadata_id'},
        header = i18n.item_table.metadata_id,
        fields = {'items.metadata_id'},
        display = h.display_value_factory{},
    },
    {
        order = 1005,
        args = {'size'},
        header = i18n.item_table.inventory_size,
        fields = {'items.size_x', 'items.size_y'},
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['items.size_x'] * data['items.size_y'])
                    :wikitext(string.format('%s×%s', data['items.size_x'], data['items.size_y']))
        end,
    },
    {
        order = 1100,
        args = {'essence'},
        header = i18n.item_table.essence_level,
        fields = {'essences.level'},
        display = h.display_value_factory{},
    },
    {
        order = 1300,
        args = {'stack_size'},
        header = i18n.item_table.stack_size,
        fields = {'stackables.stack_size'},
        display = h.display_value_factory{},
    },
    {
        order = 1301,
        args = {'stack_size_currency_tab'},
        header = i18n.item_table.stack_size_currency_tab,
        fields = {'stackables.stack_size_currency_tab'},
        display = h.display_value_factory{},
    },
    -- Requirements
    {
        order = 1400,
        args = {'level'},
        header = m_util.html.abbr(
            string.format(
                '[[%s|link=|alt=%s]]',
                i18n.item_table.level_icon,
                i18n.item_table.required_level
            ),
            i18n.item_table.required_level
        ),
        fields = h.range_fields_factory{fields={'items.required_level'}},
        display = h.display_range_factory{field='items.required_level'},
    },
    {
        order = 1401,
        args = {'str'},
        header = m_util.html.abbr(
            string.format(
                '[[%s|link=|alt=%s]]',
                i18n.item_table.str_icon,
                i18n.item_table.required_str
            ),
            i18n.item_table.required_str
        ),
        fields = h.range_fields_factory{fields={'items.required_strength'}},
        display = h.display_range_factory{field='items.required_strength'},
    },
    {
        order = 1402,
        args = {'dex'},
        header = m_util.html.abbr(
            string.format(
                '[[%s|link=|alt=%s]]',
                i18n.item_table.dex_icon,
                i18n.item_table.required_dex
            ),
            i18n.item_table.required_dex
        ),
        fields = h.range_fields_factory{fields={'items.required_dexterity'}},
        display = h.display_range_factory{field='items.required_dexterity'},
    },
    {
        order = 1403,
        args = {'int'},
        header = m_util.html.abbr(
            string.format(
                '[[%s|link=|alt=%s]]',
                i18n.item_table.int_icon,
                i18n.item_table.required_int
            ),
            i18n.item_table.required_int
        ),
        fields = h.range_fields_factory{fields={'items.required_intelligence'}},
        display = h.display_range_factory{field='items.required_intelligence'},
    },
    -- Armour 15xx
    {
        order = 1500,
        args = {'ar'},
        header = i18n.item_table.armour,
        fields = h.range_fields_factory{fields={'armours.armour'}},
        display = h.display_range_factory{field='armours.armour'},
    },
    {
        order = 1501,
        args = {'ev'},
        header =i18n.item_table.evasion,
        fields = h.range_fields_factory{fields={'armours.evasion'}},
        display = h.display_range_factory{field='armours.evasion'},
    },
    {
        order = 1502,
        args = {'es'},
        header = i18n.item_table.energy_shield,
        fields = h.range_fields_factory{fields={'armours.energy_shield'}},
        display = h.display_range_factory{field='armours.energy_shield'},
    },
    {
        order = 1503,
        args = {'wd'},
        header = i18n.item_table.ward,
        fields = h.range_fields_factory{fields={'armours.ward'}},
        display = h.display_range_factory{field='armours.ward'},
    },
    {
        order = 1504,
        args = {'block'},
        header = i18n.item_table.block,
        fields = h.range_fields_factory{fields={'shields.block'}},
        display = h.display_range_factory{field='shields.block'},
    },
    -- Weapons 16xx
    {
        order = 1600,
        args = {'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 = 1601,
        args = {'weapon', 'aps'},
        header = i18n.item_table.attacks_per_second,
        fields = h.range_fields_factory{fields={'weapons.attack_speed'}},
        display = h.display_range_factory{field='weapons.attack_speed'},
    },
    {
        order = 1602,
        args = {'weapon', 'crit'},
        header = i18n.item_table.local_critical_strike_chance,
        fields = h.range_fields_factory{fields={'weapons.critical_strike_chance'}},
        display = h.display_range_factory{field='weapons.critical_strike_chance'},
    },
    {
        order = 1603,
        args = {'physical_dps'},
        header = i18n.item_table.physical_dps,
        fields = h.range_fields_factory{fields={'weapons.physical_dps'}},
        display = h.display_range_factory{field='weapons.physical_dps'},
    },
    {
        order = 1604,
        args = {'lightning_dps'},
        header = i18n.item_table.lightning_dps,
        fields = h.range_fields_factory{fields={'weapons.lightning_dps'}},
        display = h.display_range_factory{field='weapons.lightning_dps'},
    },
    {
        order = 1605,
        args = {'cold_dps'},
        header = i18n.item_table.cold_dps,
        fields = h.range_fields_factory{fields={'weapons.cold_dps'}},
        display = h.display_range_factory{field='weapons.cold_dps'},
    },
    {
        order = 1606,
        args = {'fire_dps'},
        header = i18n.item_table.fire_dps,
        fields = h.range_fields_factory{fields={'weapons.fire_dps'}},
        display = h.display_range_factory{field='weapons.fire_dps'},
    },
    {
        order = 1607,
        args = {'chaos_dps'},
        header = i18n.item_table.chaos_dps,
        fields = h.range_fields_factory{fields={'weapons.chaos_dps'}},
        display = h.display_range_factory{field='weapons.chaos_dps'},
    },
    {
        order = 1608,
        args = {'elemental_dps'},
        header = i18n.item_table.elemental_dps,
        fields = h.range_fields_factory{fields={'weapons.elemental_dps'}},
        display = h.display_range_factory{field='weapons.elemental_dps'},
    },
    {
        order = 1609,
        args = {'poison_dps'},
        header = i18n.item_table.poison_dps,
        fields = h.range_fields_factory{fields={'weapons.poison_dps'}},
        display = h.display_range_factory{field='weapons.poison_dps'},
    },
    {
        order = 1610,
        args = {'dps'},
        header = i18n.item_table.dps,
        fields = h.range_fields_factory{fields={'weapons.dps'}},
        display = h.display_range_factory{field='weapons.dps'},
    },
    -- Flasks 17xx
    {
        order = 1700,
        args = {'flask_life'},
        header = i18n.item_table.flask_life,
        fields = h.range_fields_factory{fields={'flasks.life'}},
        display = h.display_range_factory{field='flasks.life'},
    },
    {
        order = 1701,
        args = {'flask_life_per_second'},
        header = i18n.item_table.flask_life_per_second,
        fields = h.range_fields_factory{fields={'flasks.life', 'flasks.duration'}, full=true},
        display = h.display_range_composite_factory{field1='flasks.life', field2='flasks.duration'},
    },
    {
        order = 1702,
        args = {'flask_life_per_charge'},
        header = i18n.item_table.flask_life_per_charge,
        fields = h.range_fields_factory{fields={'flasks.life', 'flasks.charges_per_use'}, full=true},
        display = h.display_range_composite_factory{field1='flasks.life', field2='flasks.charges_per_use'},
    },
    {
        order = 1703,
        args = {'flask_mana'},
        header = i18n.item_table.flask_mana,
        fields = h.range_fields_factory{fields={'flasks.mana'}},
        display = h.display_range_factory{field='flasks.mana'},
    },
    {
        order = 1704,
        args = {'flask_mana_per_second'},
        header = i18n.item_table.flask_mana_per_second,
        fields = h.range_fields_factory{fields={'flasks.mana', 'flasks.duration'}, full=true},
        display = h.display_range_composite_factory{field1='flasks.mana', field2='flasks.duration'},
    },
    {
        order = 1705,
        args = {'flask_mana_per_charge'},
        header = i18n.item_table.flask_mana_per_charge,
        fields = h.range_fields_factory{fields={'flasks.mana', 'flasks.charges_per_use'}, full=true},
        display = h.display_range_composite_factory{field1='flasks.mana', field2='flasks.charges_per_use'},
    },
    {
        order = 1706,
        args = {'flask'},
        header = i18n.item_table.flask_duration,
        fields = h.range_fields_factory{fields={'flasks.duration'}},
        display = h.display_range_factory{field='flasks.duration'},
    },
    {
        order = 1707,
        args = {'flask'},
        header = i18n.item_table.flask_charges_per_use,
        fields = h.range_fields_factory{fields={'flasks.charges_per_use'}},
        display = h.display_range_factory{field='flasks.charges_per_use'},
    },
    {
        order = 1708,
        args = {'flask'},
        header = i18n.item_table.flask_maximum_charges,
        fields = h.range_fields_factory{fields={'flasks.charges_max'}},
        display = h.display_range_factory{field='flasks.charges_max'},
    },
    -- Jewels 18xx
    {
        order = 1800,
        args = {'jewel_limit'},
        header = i18n.item_table.jewel_limit,
        fields = {'jewels.jewel_limit'},
        display = h.display_value_factory{},
    },
    {
        order = 1801,
        args = {'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,
    },
    -- Maps 19xx
    {
        order = 1900,
        args = {'map_tier'},
        header = i18n.item_table.map_tier,
        fields = {'maps.tier'},
        display = h.display_value_factory{},
    },
    {
        order = 1901,
        args = {'map_level'},
        header = i18n.item_table.map_level,
        fields = {'maps.area_level'},
        display = h.display_value_factory{},
    },
    {
        order = 1902,
        args = {'map_guild_character'},
        header = i18n.item_table.map_guild_character,
        fields = {'maps.guild_character'},
        display = h.display_value_factory{},
        sort_type = 'text',
    },
    {
        order = 1903,
        args = {'map_series'},
        header = i18n.item_table.map_series,
        fields = {'maps.series'},
        display = h.display_value_factory{},
    },
    {
        order = 1904,
        args = {'atlas_tier'},
        header = i18n.item_table.atlas_tier,
        fields = {'atlas_maps.map_tier0', 'atlas_maps.map_tier1', 'atlas_maps.map_tier2', 'atlas_maps.map_tier3', 'atlas_maps.map_tier4'},
        display = h.display_atlas_tier_factory{is_level=false},
        colspan = 5,
    },
    {
        order = 1905,
        args = {'atlas_level'},
        header = i18n.item_table.atlas_level,
        fields = {'atlas_maps.map_tier0', 'atlas_maps.map_tier1', 'atlas_maps.map_tier2', 'atlas_maps.map_tier3', 'atlas_maps.map_tier4'},
        display = h.display_atlas_tier_factory{is_level=true},
        colspan = 5,
    },
    -- Map fragments 20xx
    {
        order = 2000,
        args = {'map_fragment', 'map_fragment_limit'},
        header = i18n.item_table.map_fragment_limit,
        fields = {'map_fragments.map_fragment_limit'},
        display = h.display_value_factory{},
    },
    -- Hideout decorations 21xx
    {
        order = 2100,
        args = {'doodad', 'variation_count'},
        header = i18n.item_table.variation_count,
        fields = {'hideout_doodads.variation_count'},
        display = h.display_value_factory{
            color = 'mod',
        },
    },
    -- Corpse items 22xx
    {
        order = 2200,
        args = {'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 = 2201,
        args = {'corpse', 'monster_abilities'},
        header = i18n.item_table.monster_abilities,
        fields = {'corpse_items.monster_abilities'},
        display = h.display_value_factory{
            color = 'mod',
        },
        sort_type = 'text',
    },
    -- Seed data 91xx,
    {
        order = 9100,
        args = {'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 = 9101,
        args = {'seed', 'seed_tier'},
        header = i18n.item_table.seed_tier,
        fields = {'harvest_seeds.tier'},
        display = h.display_value_factory{},
    },
    {
        order = 9102,
        args = {'seed', 'seed_growth_cycles'},
        header = i18n.item_table.seed_growth_cycles,
        fields = {'harvest_seeds.growth_cycles'},
        display = h.display_value_factory{},
    },
    {
        order = 9110,
        args = {'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.display_value_factory{
            color = 'primal',
        },
    },
    {
        order = 9110,
        args = {'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.display_value_factory{
            color = 'vivid',
        },
    },
    {
        order = 9110,
        args = {'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.display_value_factory{
            color = 'wild',
        },
    },
    {
        order = 9113,
        args = {'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.display_value_factory{},
    },
    {
        order = 9114,
        args = {'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.display_value_factory{},
    },
    {
        order = 12000,
        args = {'buff'},
        header = i18n.item_table.buff_effects,
        fields = {'item_buffs.stat_text'},
        display = h.display_value_factory{
            color = 'mod',
        },
        sort_type = 'text',
    },
    {
        order = 12001,
        args = {'stat'},
        header = i18n.item_table.stats,
        fields = {'items.stat_text'},
        display = h.display_value_factory{
            color = 'mod',
        },
        sort_type = 'text',
    },
    {
        order = 12002,
        args = {'description'},
        header = i18n.item_table.description,
        fields = {'items.description'},
        display = h.display_value_factory{
            color = 'mod',
        },
        sort_type = 'text',
    },
    {
        order = 12003,
        args = {'seed', 'seed_effect'},
        header = i18n.item_table.seed_effects,
        fields = {'harvest_seeds.effect'},
        display = h.display_value_factory{
            color = 'crafted',
        },
        sort_type = 'text',
    },
    {
        order = 12100,
        args = {'flavour_text'},
        header = i18n.item_table.flavour_text,
        fields = {'items.flavour_text'},
        display = h.display_value_factory{
            color = 'flavour',
        },
        sort_type = 'text',
    },
    {
        order = 12200,
        args = {'help_text'},
        header = i18n.item_table.help_text,
        fields = {'items.help_text'},
        display = h.display_value_factory{
            color = 'help',
        },
        sort_type = 'text',
    },
    {
        order = 12300,
        args = {'buff_icon'},
        header = i18n.item_table.buff_icon,
        fields = {'item_buffs.icon'},
        display = h.display_value_factory{
            fmt_options = {
                [1] = {
                    fmt = '[[%s]]',
                },
            },
        },
        sort_type = 'text',
    },
    {
        order = 13000,
        args = {'prophecy', 'objective'},
        header = i18n.item_table.objective,
        fields = {'prophecies.objective'},
        display = h.display_value_factory{},
    },
    {
        order = 13001,
        args = {'prophecy', 'reward'},
        header = i18n.item_table.reward,
        fields = {'prophecies.reward'},
        display = h.display_value_factory{},
    },
    {
        order = 13002,
        args = {'prophecy', 'seal_cost'},
        header = i18n.item_table.seal_cost,
        fields = {'prophecies.seal_cost'},
        display = h.display_value_factory{
            color = 'currency',
        },
    },
    {
        order = 13003,
        args = {'prediction_text'},
        header = i18n.item_table.prediction_text,
        fields = {'prophecies.prediction_text'},
        display = h.display_value_factory{
            color = 'value',
        },
        sort_type = 'text',
    },
    {
        order = 14000,
        args = {'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,
        args = {'drop', 'drop_level'},
        header = i18n.item_table.drop_level,
        fields = {'items.drop_level'},
        display = h.display_value_factory{},
    },
    {
        order = 15001,
        args = {'drop_level_maximum'},
        header = i18n.item_table.drop_level_maximum,
        fields = {'items.drop_level_maximum'},
        display = h.display_value_factory{},
    },
    {
        order = 15002,
        args = {'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 = 15003,
        args = {'drop', 'drop_enabled'},
        header = i18n.item_table.drop_enabled,
        fields = {'items.drop_enabled'},
        display = h.display_value_factory{},
        display = h.display_yesno_factory{
            field = 'items.drop_enabled',
        },
        sort_type = 'text',
    },
    {
        order = 15004,
        args = {'drop', 'drop_areas'},
        header = i18n.item_table.drop_areas,
        fields = {'items.drop_areas_html'},
        display = h.display_value_factory{},
        sort_type = 'text',
    },
    {
        order = 15005,
        args = {'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,
        sort_type = 'text',
    },
    {
        order = 15006,
        args = {'drop', 'drop_text'},
        header = i18n.item_table.drop_text,
        fields = {'items.drop_text'},
        display = h.display_value_factory{},
        sort_type = 'text',
    },
    {
        order = 16000,
        args = {'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:node(m_util.html.table_cell('na'))
            else
                tr
                    :tag('td')
                        :attr('style', 'text-align:left')
                        :wikitext(value)
            end
        end,
        sort_type = 'text',
    },
    {
        order = 17000,
        args = {'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:node(m_util.html.table_cell('na'))
            else
                tr
                    :tag('td')
                        :attr('style', 'text-align:left')
                        :wikitext(value)
            end
        end,
        sort_type = 'text',
    },
    {
        order = 18000,
        args = {'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,
        sort_type = 'text',
    },
    {
        order = 18001,
        args = {'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,
        sort_type = 'text',
    },
    {
        order = 19000,
        args = {'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,
        sort_type = 'text',
    },
    {
        order = 19001,
        args = {'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
            tr
                :tag('td')
                    :attr('data-sort-value', #tbl)
                    :wikitext(#tbl)
        end,
    },
    {
        order = 20000,
        args = {'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,
        sort_type = 'text',
    },
    {
        order = 21000,
        args = {'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,
        sort_type = 'text',
    },
    {
        order = 23000,
        args = {'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,
        sort_type = 'text',
    },
}

data_map.skill_gem = {
    {
        order = 1000,
        args = {'gem_tier'},
        header = i18n.item_table.tier,
        fields = {'skill_gems.gem_tier'},
        display = h.display_value_factory{},
    },
    {
        order = 1001,
        args = {'skill_icon'},
        header = i18n.item_table.skill_icon,
        fields = {'skill.skill_icon'},
        display = h.display_value_factory{
            fmt_options = {
                [1] = {
                    fmt = '[[%s]]',
                },
            },
        },
        sort_type = 'text',
    },
    {
        order = 2000,
        args = {'stat', 'stat_text'},
        header = i18n.item_table.stats,
        fields = {'skill.stat_text'},
        display = h.display_value_factory{},
        sort_type = 'text',
    },
    {
        order = 2001,
        args = {'quality', 'quality_stat_text'},
        header = i18n.item_table.quality_stats,
        fields = {'skill.quality_stat_text'},
        display = h.display_value_factory{},
        sort_type = 'text',
    },
    {
        order = 2100,
        args = {'description'},
        header = i18n.item_table.description,
        fields = {'skill.description'},
        display = h.display_value_factory{},
        sort_type = 'text',
    },
    {
        order = 3000,
        args = {'level'},
        header = m_util.html.abbr(
            string.format(
                '[[%s|link=|alt=%s]]',
                i18n.item_table.level_icon,
                i18n.item_table.gem_level_requirement
            ),
            i18n.item_table.gem_level_requirement
        ),
        fields = h.range_fields_factory{fields={'items.required_level'}},
        display = h.display_range_factory{field='items.required_level'},
    },
    {
        order = 3001,
        args = {'str'},
        header = m_util.html.abbr(
            string.format(
                '[[%s|link=|alt=%s]]',
                i18n.item_table.str_icon,
                i18n.item_table.str_gem
            ),
            i18n.item_table.str_gem
        ),
        fields = {'skill_gems.strength_percent'},
        display = h.display_yesno_factory{
            field = 'skill_gems.strength_percent',
            condition = h.value_greater_than_zero,
        },
        sort_type = 'text',
    },
    {
        order = 3002,
        args = {'dex'},
        header = m_util.html.abbr(
            string.format(
                '[[%s|link=|alt=%s]]',
                i18n.item_table.dex_icon,
                i18n.item_table.dex_gem
            ),
            i18n.item_table.dex_gem
        ),
        fields = {'skill_gems.dexterity_percent'},
        display = h.display_yesno_factory{
            field = 'skill_gems.dexterity_percent',
            condition = h.value_greater_than_zero,
        },
        sort_type = 'text',
    },
    {
        order = 3003,
        args = {'int'},
        header = m_util.html.abbr(
            string.format(
                '[[%s|link=|alt=%s]]',
                i18n.item_table.int_icon,
                i18n.item_table.int_gem
            ),
            i18n.item_table.int_gem
        ),
        fields = {'skill_gems.intelligence_percent'},
        display = h.display_yesno_factory{
            field = 'skill_gems.intelligence_percent',
            condition = h.value_greater_than_zero,
        },
        sort_type = 'text',
    },
    {
        order = 4000,
        args = {'crit'},
        header = i18n.item_table.skill_critical_strike_chance,
        fields = {'skill_levels.critical_strike_chance'},
        display = h.display_value_factory{
            fmt_options = {
                [1] = {
                    fmt = '%s%%',
                },
            },
        },
        field_options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        order = 4001,
        args = {'cast_time'},
        header = i18n.item_table.cast_time,
        fields = {'skill.cast_time'},
        display = h.display_value_factory{},
    },
    {
        order = 4002,
        args = {'aspd', 'attack_speed', 'attack_speed_multiplier'},
        header = i18n.item_table.attack_speed_multiplier,
        fields = {'skill_levels.attack_speed_multiplier'},
        display = h.display_value_factory{
            fmt_options = {
                [1] = {
                    fmt = '%s%%',
                },
            },
        },
        field_options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        order = 4003,
        args = {'dmgeff'},
        header = i18n.item_table.damage_effectiveness,
        fields = {'skill_levels.damage_effectiveness'},
        display = h.display_value_factory{
            fmt_options = {
                [1] = {
                    fmt = '%s%%',
                },
            },
        },
        field_options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        order = 5000,
        args = {'mcm', 'cost_multiplier'},
        header = i18n.item_table.cost_multiplier,
        fields = {'skill_levels.cost_multiplier'},
        display = h.display_value_factory{
            fmt_options = {
                [1] = {
                    fmt = '%s%%',
                },
            },
        },
        field_options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        order = 5001,
        args = {'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.display_value_factory{
                fmt_options = {
                    [1] = {
                        fmt = '%d' .. appendix,
                    },
                },
            }(tr, data, {cost_field}, data2)
        end,
        -- Need one set of options per field.
        field_options = {
            [1] = {
                skill_levels = true,
            },
            [2] = {
                skill_levels = true,
            },
            [3] = {
                skill_levels = true,
            },
        },
    },
    {
        order = 6000,
        args = {'vaal'},
        header = i18n.item_table.vaal_souls_requirement,
        fields = {'skill_levels.vaal_souls_requirement'},
        display = h.display_value_factory{},
        field_options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        order = 6001,
        args = {'vaal'},
        header = i18n.item_table.stored_uses,
        fields = {'skill_levels.vaal_stored_uses'},
        display = h.display_value_factory{},
        field_options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        order = 7000,
        args = {'radius'},
        header = i18n.item_table.primary_radius,
        fields = {'skill.radius', 'skill.radius_description'},
        field_options = {
            [2] = {
                optional = true,
            },
        },
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['skill.radius'])
                    :wikitext(h.display_descriptor_value_factory{tbl=data, key='skill.radius_description'}(data['skill.radius']))
        end,
    },
    {
        order = 7001,
        args = {'radius'},
        header = i18n.item_table.secondary_radius,
        fields = {'skill.radius_secondary', 'skill.radius_secondary_description'},
        field_options = {
            [2] = {
                optional = true,
            },
        },
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['skill.radius_secondary'])
                    :wikitext(h.display_descriptor_value_factory{tbl=data, key='skill.radius_secondary_description'}(data['skill.radius_secondary']))
        end,
    },
    {
        order = 7002,
        args = {'radius'},
        header = i18n.item_table.tertiary_radius,
        fields = {'skill.radius_tertiary', 'skill.radius_tertiary_description'},
        field_options = {
            [2] = {
                optional = true,
            },
        },
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['skill.radius_tertiary'])
                   :wikitext(h.display_descriptor_value_factory{tbl=data, key='skill.radius_tertiary_description'}(data['skill.radius_tertiary']))
        end,
    },
}

-- ----------------------------------------------------------------------------
-- 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

    -- Handle deprecated "q_" args
    local query_params = {
        'tables',
        'join',
        'where',
        'groupBy',
        'having',
        'orderBy',
        'limit',
        'offset',
    }
    for _, v in ipairs(query_params) do
        args[v] = args[v] or args['q_' .. v]
    end

    -- A where clause is required; there are far too many items to list in one table
    if args.where == nil then
        error(string.format(i18n.errors.generic_required_parameter, 'where'))
    end

    local results2 = {
        stats = {},
        skill_levels = {},
        pageIDs = {},
    }

    local row_infos = {}
    for _, row_info in ipairs(modes[args.mode].data) do
        local enabled = false
        if type(row_info.args) == 'table' then
            for _, a in ipairs(row_info.args) do
                if m_util.cast.boolean(args[a]) then
                    enabled = true
                    break
                end
            end
        else
            enabled = true
        end
        if enabled then
            row_info.field_options = row_info.field_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 = {
            header = col_info.header,
            fields = {},
            display = function (tr, data)
                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, {color=false})
                            vmax = vmax + stat.max
                        end
                    end

                    if num_stats ~= #stat_texts then
                        tr:node(m_util.html.table_cell('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)

    -- Build Cargo query
    local tables = {'items'}
    local fields = {
        'items._pageID',
        'items._pageName',
        'items.name',
        'items.inventory_icon',
        'items.html',
        'items.size_x',
        'items.size_y',
    }
    local query = {
        where = args.where,
        groupBy = table.concat({'items._pageID', args.groupBy}, ', '),
        having = args.having,
        orderBy = args.orderBy,
        limit = args.limit,
        offset = args.offset,
    }

    -- 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

    -- Minimum required tables and fields, based on display options
    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.field_options[index] = rowinfo.field_options[index] or {}
            if rowinfo.field_options[index].skill_levels then
                skill_levels[#skill_levels+1] = field
            else
                fields[#fields+1] = field
                tables[#tables+1] = m_util.string.split(field, '.', true)[1]
            end
        end
    end
    if #skill_levels > 0 then
        fields[#fields+1] = 'skill.max_level'
        tables[#tables+1] = 'skill'
    end
    tables = m_util.table.remove_duplicates(tables)

    -- Minimum required joins, based on display options
    local joins = {}
    for _, table_name in ipairs(tables) do
        if table_name ~= 'items' then
            joins[#joins+1] = string.format('items._pageID=%s._pageID', table_name)
        end
    end

    -- Append additional tables
    args.tables = m_util.cast.table(args.tables)
    if type(args.tables) == 'table' and #args.tables > 0 then
        tables = m_util.table.merge(tables, args.tables)
    end

    -- Make join clause
    if #joins > 0 or args.join then
        -- m_util.table.merge rebuilds the table, which removes empty values
        query.join = table.concat(m_util.table.merge(joins, {args.join}), ', ')
    end

    -- Query results
    local results = m_cargo.query(tables, 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 options = rowinfo.field_options[index]
                    if options.optional ~= true and options.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:node(m_util.html.table_cell('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, {tables=tables, fields=fields, query=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)

--
-- Template:Map item drops
--
p.map_item_drops = m_util.misc.invoker_factory(_map_item_drops, {
    wrappers = cfg.wrappers.map_item_drops,
})

--
-- Template:Prophecy description
--
p.prophecy_description = m_util.misc.invoker_factory(_prophecy_description, {
    wrappers = cfg.wrappers.prophecy_description,
})

--
-- Template:Simple item list
--
p.simple_item_list = m_util.misc.invoker_factory(_simple_item_list, {
    wrappers = cfg.wrappers.simple_item_list,
})

-- ----------------------------------------------------------------------------
-- 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