Module:Item table: Difference between revisions

From Path of Exile 2 Wiki
Jump to navigation Jump to search
>Illviljan
m (Link to the league page.)
No edit summary
 
(71 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 f_skill_link = require('Module:Skill link').skill_link
local m_cargo = require('Module:Cargo')
-- ----------------------------------------------------------------------------
-- Globals
-- ----------------------------------------------------------------------------
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 = {
-- Should we use the sandbox version of our submodules?
    categories = {
local use_sandbox = m_util.misc.maybe_sandbox('Item table')
        -- 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_cargo = use_sandbox and require('Module:Cargo/sandbox') or require('Module:Cargo')
    item_table = {
local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or mw.loadData('Module:Game')
        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_leagues_link = '[[%s league|%s]]',
        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}{sl}',
        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 = use_sandbox and require('Module:Item link/sandbox').item_link or 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 = use_sandbox and require('Module:Skill link/sandbox').skill_link or 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 241: 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 269: Line 142:
end
end


function h.tbl.display.factory.range(args)
function h.display_range_factory(args)
     -- args: table
     -- args: table
     --  property
     --  property
Line 282: 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.tooltip(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')
        arg = 'stack_size',
                    :attr('data-sort-value', data['items.base_item'])
        header = i18n.item_table.stack_size,
                    :wikitext(string.format('[[%s|%s]]', data['items.base_item_page'], data['items.base_item']))
        fields = {'stackables.stack_size'},
         end,
         display = h.tbl.display.factory.value{},
         sort_type = 'text',
         order = 4000,
     },
     },
     {
     {
         arg = 'stack_size_currency_tab',
         order = 1001,
         header = i18n.item_table.stack_size_currency_tab,
        args = {'class'},
         fields = {'stackables.stack_size_currency_tab'},
         header = i18n.item_table.item_class,
         display = h.tbl.display.factory.value{},
         fields = {'items.class'},
         order = 4001,
         display = h.display_value_factory{
            fmt_options = {
                [1] = {
                    fmt = '[[%s]]',
                },
            },
        },
         sort_type = 'text',
     },
     },
     {
     {
         arg = 'level',
         order = 1002,
         header = m_game.level_requirement.icon,
        args = {'rarity'},
         fields = h.tbl.range_fields('items.required_level'),
         header = i18n.item_table.rarity,
         display = h.tbl.display.factory.range{field='items.required_level'},
         fields = {'items.rarity'},
        order = 5000,
         display = h.display_value_factory{},
     },
     },
     {
     {
         arg = 'ar',
         order = 1003,
         header = i18n.item_table.armour,
        args = {'rarity_id'},
         fields = h.tbl.range_fields('armours.armour'),
         header = i18n.item_table.rarity_id,
         display = h.tbl.display.factory.range{field='armours.armour'},
         fields = {'items.rarity_id'},
        order = 6000,
         display = h.display_value_factory{},
     },
     },
     {
     {
         arg = 'ev',
         order = 1004,
         header =i18n.item_table.evasion,
        args = {'metadata_id'},
         fields = h.tbl.range_fields('armours.evasion'),
         header = i18n.item_table.metadata_id,
         display = h.tbl.display.factory.range{field='armours.evasion'},
         fields = {'items.metadata_id'},
        order = 6001,
         display = h.display_value_factory{},
     },
     },
     {
     {
        arg = 'es',
         order = 1005,
        header = i18n.item_table.energy_shield,
         args = {'size'},
        fields = h.tbl.range_fields('armours.energy_shield'),
         header = i18n.item_table.inventory_size,
        display = h.tbl.display.factory.range{field='armours.energy_shield'},
         fields = {'items.size_x', 'items.size_y'},
        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.tooltip(
         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.tooltip(
         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.tooltip(
         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.tooltip(
         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'},
     },
     },
     {
     {
         arg = {'doodad', 'variation_count'},
         order = 1611,
         header = i18n.item_table.variation_count,
        args = {'reload_time'},
         fields = {'hideout_doodads.variation_count'},
         header = i18n.item_table.reload_time,
         display = h.tbl.display.factory.value{colour='mod'},
         fields = h.range_fields_factory{fields={'weapons.reload_time'}},
        order = 11105,
         display = h.display_range_factory{field='weapons.reload_time'},
     },
     },
    -- Flasks 17xx
     {
     {
         arg = 'buff',
         order = 1700,
         header = i18n.item_table.buff_effects,
        args = {'flask_life'},
         fields = {'item_buffs.stat_text'},
         header = i18n.item_table.flask_life,
         display = h.tbl.display.factory.value{colour='mod'},
         fields = h.range_fields_factory{fields={'flasks.life'}},
        order = 12000,
         display = h.display_range_factory{field='flasks.life'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'stat',
         order = 1701,
         header = i18n.item_table.stats,
        args = {'flask_life_per_second'},
         fields = {'items.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 = 12001,
         display = h.display_range_composite_factory{field1='flasks.life', field2='flasks.duration'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'description',
         order = 1702,
         header = i18n.item_table.effects,
        args = {'flask_life_per_charge'},
         fields = {'items.description'},
         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 = 12002,
         display = h.display_range_composite_factory{field1='flasks.life', field2='flasks.charges_per_use'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'incubator_effect',
         order = 1703,
         header = i18n.item_table.incubator_effect,
        args = {'flask_mana'},
         fields = {'incubators.effect'},
         header = i18n.item_table.flask_mana,
         display = h.tbl.display.factory.value{colour='crafted'},
         fields = h.range_fields_factory{fields={'flasks.mana'}},
        order = 12004,
         display = h.display_range_factory{field='flasks.mana'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'flavour_text',
         order = 1704,
         header = i18n.item_table.flavour_text,
        args = {'flask_mana_per_second'},
         fields = {'items.flavour_text'},
         header = i18n.item_table.flask_mana_per_second,
         display = h.tbl.display.factory.value{colour='flavour'},
         fields = h.range_fields_factory{fields={'flasks.mana', 'flasks.duration'}, full=true},
        order = 12006,
         display = h.display_range_composite_factory{field1='flasks.mana', field2='flasks.duration'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = 'help_text',
         order = 1705,
         header = i18n.item_table.help_text,
        args = {'flask_mana_per_charge'},
         fields = {'items.help_text'},
         header = i18n.item_table.flask_mana_per_charge,
         display = h.tbl.display.factory.value{colour='help'},
         fields = h.range_fields_factory{fields={'flasks.mana', 'flasks.charges_per_use'}, full=true},
        order = 12008,
         display = h.display_range_composite_factory{field1='flasks.mana', field2='flasks.charges_per_use'},
        sort_type = 'text',
     },
     },
     {
     {
         arg = {'prophecy', 'objective'},
         order = 1706,
         header = i18n.item_table.objective,
        args = {'flask'},
         fields = {'prophecies.objective'},
         header = i18n.item_table.flask_duration,
         display = h.tbl.display.factory.value{},
         fields = h.range_fields_factory{fields={'flasks.duration'}},
        order = 13002,
         display = h.display_range_factory{field='flasks.duration'},
     },
     },
         {
    {
         arg = {'prophecy', 'reward'},
         order = 1707,
         header = i18n.item_table.reward,
         args = {'flask'},
         fields = {'prophecies.reward'},
         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 = 13001,
         display = h.display_range_factory{field='flasks.charges_per_use'},
     },
     },
     {
     {
         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(
                            i18n.item_table.version_link,
                            data['items.release_version'],
                            data['items.release_version']
                        )
                    )
        end,
        order = 15000,
     },
     },
     {
     {
         arg = {'version', 'removal_version'},
         order = 1901,
         header = i18n.item_table.removal_version,
        args = {'map_level'},
         fields = {'items.removal_version'},
         header = i18n.item_table.map_level,
         display = function(tr, data)
         fields = {'maps.area_level'},
            tr
         display = h.display_value_factory{},
                :tag('td')
                    :wikitext(
                        string.format(
                            i18n.item_table.version_link,
                            data['items.removal_version'],
                            data['items.removal_version']
                        )
                    )
        end,
        order = 15001,
     },
     },
     {
     {
         arg = {'drop', 'drop_enabled'},
         order = 1902,
         header = i18n.item_table.drop_enabled,
        args = {'map_guild_character'},
         fields = {'items.drop_enabled'},
         header = i18n.item_table.map_guild_character,
         display = h.tbl.display.factory.value{},
         fields = {'maps.guild_character'},
         order = 15002,
         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{},
     },
     },
     {
     {
         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'},
            local s = m_util.string.split(data['items.drop_leagues'], ',')
         display = h.display_atlas_tier_factory{is_level=false},
            for i, v in ipairs(s) do
         colspan = 5,
                s[i] = string.format(i18n.item_table.drop_leagues_link, v, v)
            end
            tr
                :tag('td')
                    :wikitext(table.concat(s, '<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
           
            value = table.concat(tbl, '<br>')
            if value == nil or value == '' then
                tr:wikitext(m_util.html.td.na())
            else
                tr
                    :tag('td')
                        :attr('style', 'text-align:left')
                        :wikitext(value)
            end
         end,
         order = 17001,
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'price', 'purchase_cost'},
         order = 9110,
         header = i18n.item_table.purchase_costs,
        args = {'seed', 'seed_cosumed_lifeforce', 'seed_consumed_lifeforce_percentage', 'seed_consumed_wild_lifeforce_percentage'},
         fields = {'item_purchase_costs.name', 'item_purchase_costs.amount'},
         header = i18n.item_table.seed_consumed_wild_lifeforce_percentage,
         display = function (tr, data)
         fields = {'harvest_seeds.consumed_wild_lifeforce_percentage'},
            -- 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 = 'wild',
            local tbl = {}
        },
            if data['item_purchase_costs.name'] ~= nil then
    },
                tbl[#tbl+1] = string.format(
    {
                        '%sx %s',  
         order = 9113,
                        data['item_purchase_costs.amount'],
        args = {'seed', 'seed_required_nearby_seeds', 'seed_required_nearby_seed_amount'},
                        f_item_link{data['item_purchase_costs.name']}
        header = i18n.item_table.seed_required_nearby_seed_amount,
                    )
        fields = {'harvest_seeds.required_nearby_seed_amount'},
            end
        display = h.display_value_factory{},
            h.na_or_val(tr, table.concat(tbl, '<br>'))
    },
         end,
    {
         order = 18001,
        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', 'sell_price'},
         order = 12001,
         header = i18n.item_table.sell_price,
        args = {'stat'},
         fields = {'item_sell_prices.name', 'item_sell_prices.amount'},
         header = i18n.item_table.stats,
         display = function(tr, data, na, results2)
         fields = {'items.stat_text'},
             if results2['sell_price_query'] == nil then
         display = h.display_value_factory{
                results2['sell_price_query'] = m_cargo.query(
             color = 'mod',
                    {'items', 'item_sell_prices'},
        },
                    {
        sort_type = 'text',
                        'item_sell_prices.name',
    },
                        'item_sell_prices.amount',  
    {
                        'item_sell_prices._pageID'
        order = 12002,
                    },
        args = {'description'},
                    {
        header = i18n.item_table.description,
                        join='items._pageID=item_sell_prices._pageID',
        fields = {'items.description'},
                        where=string.format(
        display = h.display_value_factory{
                            'items._pageID IN (%s) AND item_sell_prices._pageID IS NOT NULL',  
            color = 'mod',
                            table.concat(results2.pageIDs, ', ')
        },
                        ),
        sort_type = 'text',
                        orderBy='item_sell_prices.name',
    },
                    }
    {
                )
        order = 12003,
                results2['sell_price_query'] = m_cargo.map_results_to_id{
        args = {'seed', 'seed_effect'},
                    results=results2['sell_price_query'],  
        header = i18n.item_table.seed_effects,
                    field='item_sell_prices._pageID',
        fields = {'harvest_seeds.effect'},
                }
        display = h.display_value_factory{
            end
             color = 'crafted',
            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',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'boss', 'boss_name'},
         order = 12100,
         header = i18n.item_table.boss_name,
        args = {'flavour_text'},
         fields = {'maps.area_id'},  
         header = i18n.item_table.flavour_text,
         display = function(tr, data, na, results2)
         fields = {'items.flavour_text'},
             if results2['boss_query'] == nil then
         display = h.display_value_factory{
                results2['boss_query'] = m_cargo.query(
             color = 'flavour',
                    {'items', 'maps', 'areas', 'monsters', 'main_pages'},
        },
                    {
        sort_type = 'text',
                        'items._pageName',  
    },
                        'maps.area_id',  
    {
                        'areas.id',
        order = 12200,
                        'areas.boss_monster_ids',
        args = {'help_text'},
                        'monsters._pageName',
        header = i18n.item_table.help_text,
                        'monsters.name',
        fields = {'items.help_text'},
                        'main_pages._pageName',
        display = h.display_value_factory{
                    },
            color = 'help',
                    {
        },
                        join=[[
        sort_type = 'text',
                            items._pageID=maps._pageID,  
    },
                            maps.area_id=areas.id,  
    {
                            areas.boss_monster_ids HOLDS monsters.metadata_id,
        order = 12300,
                            monsters.metadata_id=main_pages.id
        args = {'buff_icon'},
                        ]],
        header = i18n.item_table.buff_icon,
                        where=string.format([[
        fields = {'item_buffs.icon'},
                            items._pageID IN (%s)
        display = h.display_value_factory{
                            AND maps.area_id IS NOT NULL
             fmt_options = {
                            AND areas.boss_monster_ids HOLDS LIKE "%%"
                 [1] = {
                        ]],
                    fmt = '[[%s]]',
                            table.concat(results2.pageIDs, ', ')
                },
                        ),
             },
                        orderBy='areas.boss_monster_ids',
         },
                    }
                )               
               
                results2['boss_query'] = m_cargo.map_results_to_id{
                    results=results2['boss_query'],
                    field='items._pageName',
                }
            end
            local results = results2['boss_query'][data['items._pageName']] or {}
             local tbl = {}
            for _,v in ipairs(results) do
                 local page = v['main_pages._pageName'] or v['monsters._pageName'] or ''
                local name = v['monsters.name'] or v['areas.boss_monster_ids'] or ''
                tbl[#tbl+1] = string.format('[[%s|%s]]', page, name)
             end
            h.na_or_val(tr, table.concat(tbl, '<br>'))
         end,
        order = 19001,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'boss', 'boss_number'},
         order = 13000,
         header = i18n.item_table.boss_number,
        args = {'prophecy', 'objective'},
         fields = {'maps.area_id'},  
         header = i18n.item_table.objective,
         display = function(tr, data, na, results2)
         fields = {'prophecies.objective'},
            if results2['boss_query'] == nil then
         display = h.display_value_factory{},
                results2['boss_query'] = m_cargo.query(
    },
                    {'items', 'maps', 'areas', 'monsters', 'main_pages'},
    {
                    {
        order = 13001,
                        'items._pageName',
        args = {'prophecy', 'reward'},
                        'maps.area_id',
        header = i18n.item_table.reward,
                        'areas.id',
        fields = {'prophecies.reward'},
                        'areas.boss_monster_ids',
        display = h.display_value_factory{},
                        'monsters._pageName',
                        'monsters.name',
                        'main_pages._pageName',
                    },
                    {
                        join=[[
                            items._pageID=maps._pageID,  
                            maps.area_id=areas.id,
                            areas.boss_monster_ids HOLDS monsters.metadata_id,
                            monsters.metadata_id=main_pages.id
                        ]],
                        where=string.format([[
                            items._pageID IN (%s)
                            AND maps.area_id IS NOT NULL
                            AND areas.boss_monster_ids HOLDS LIKE "%%"
                        ]],
                            table.concat(results2.pageIDs, ', ')
                        ),
                        orderBy='areas.boss_monster_ids',
                    }
                )
               
                results2['boss_query'] = m_cargo.map_results_to_id{
                    results=results2['boss_query'],
                    field='items._pageName',
                }
            end
            local results = results2['boss_query'][data['items._pageName']] or {}
            local tbl = {}
            for _,v in ipairs(results) do
                tbl[#tbl+1] = v['areas.boss_monster_ids']
            end
            h.na_or_val(tr, #tbl)
        end,
        order = 19002,
     },
     },
     {
     {
         arg = {'sextant'},
         order = 13002,
         header = i18n.item_table.sextant,
        args = {'prophecy', 'seal_cost'},
         fields = {'items.name', 'atlas_maps.x', 'atlas_maps.y', 'maps.series'},  
         header = i18n.item_table.seal_cost,
         display = function(tr, data, na, results2)
         fields = {'prophecies.seal_cost'},
             if results2['sextant_query'] == nil then
         display = h.display_value_factory{
                results2['sextant_query'] = m_cargo.query(
             color = 'currency',
                    {'items', 'maps', 'atlas_maps'},
        },
                    {
    },
                        'items._pageName',
    {
                        'items.name',
        order = 13003,
                        'maps.series',
        args = {'prediction_text'},
                        'atlas_maps.x',
        header = i18n.item_table.prediction_text,
                        'atlas_maps.y',
        fields = {'prophecies.prediction_text'},
                        'atlas_maps.connections'
        display = h.display_value_factory{
                    },
             color = 'value',
                    {
         },
                        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',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'legacy'},
         order = 14000,
         header = i18n.item_table.legacy,
        args = {'version', 'release_version'},
         fields = {'items.name'},  
         header = i18n.item_table.release_version,
         display = function(tr, data, na, results2)
         fields = {'items.release_version'},
             if results2['legacy_query'] == nil then
         display = function(tr, data)
                 results2['legacy_query'] = m_cargo.query(
             tr
                    {'items', 'legacy_variants'},
                 :tag('td')
                     {
                     :wikitext(
                         'items._pageID',
                         string.format(
                        'items._pageName',  
                            i18n.item_table.version_link,
                        'items.frame_type',  
                            data['items.release_version'],
                        'legacy_variants.removal_version',
                            data['items.release_version']
                         'legacy_variants.implicit_stat_text',
                         )
                        'legacy_variants.explicit_stat_text',
                    )
                        'legacy_variants.stat_text',  
        end,
                        'legacy_variants.base_item',
    },
                        'legacy_variants.required_level'
    {
                    },
        order = 15000,
                    {
        args = {'drop', 'drop_level'},
                        join='items._pageID=legacy_variants._pageID',
        header = i18n.item_table.drop_level,
                        where='legacy_variants.removal_version IS NOT NULL',
        fields = {'items.drop_level'},
                        where=string.format(
        display = h.display_value_factory{},
                            'items._pageID IN (%s) AND legacy_variants.removal_version IS NOT NULL',  
    },
                            table.concat(results2.pageIDs, ', ')
    {
                        ),
        order = 15001,
                        orderBy='items._pageName',
        args = {'drop_level_maximum'},
                    }
        header = i18n.item_table.drop_level_maximum,
                )
        fields = {'items.drop_level_maximum'},
               
        display = h.display_value_factory{},
                results2['legacy_query'] = m_cargo.map_results_to_id{
    },
                    results=results2['legacy_query'],  
    {
                    field='items._pageName',
        order = 15002,
                }
        args = {'version', 'removal_version'},
            end
        header = i18n.item_table.removal_version,
            local results = results2['legacy_query'][data['items._pageName']] or {}
        fields = {'items.removal_version'},
           
        display = function(tr, data)
            local tbl = mw.html.create('table')
            tr
                :attr('width', '100%')
                :tag('td')
            for _, v in ipairs(results) do
                     :wikitext(
                local cell = {}
                        string.format(
                local l = {
                            i18n.item_table.version_link,
                    'legacy_variants.base_item',  
                            data['items.removal_version'],
                    'legacy_variants.stat_text'
                            data['items.removal_version']
                }
                        )
               
                     )
                -- Clean up data:
        end,
                for _, k in ipairs(l) do
    },
                    if v[k] ~= nil then
    {
                        local s = m_util.string.split(v[k], '*')
        order = 15003,
                        local s_flt = {}
        args = {'drop', 'drop_enabled'},
                        for _, sss in ipairs(s) do
        header = i18n.item_table.drop_enabled,
                            if sss ~= nil and sss ~= '' then
        fields = {'items.drop_enabled'},
                                s_flt[#s_flt+1] = string.gsub(sss, '\n', '')
        display = h.display_value_factory{},
                            end
        display = h.display_yesno_factory{
                        end
            field = 'items.drop_enabled',
                       
        },
                        cell[#cell+1] = table.concat(s_flt, '<br>')
        sort_type = 'text',
                     end
    },
                end
    {
               
        order = 15004,
                local sep = string.format(
        args = {'drop', 'drop_areas'},
                    '<span class="item-stat-separator -%s"></span>',  
        header = i18n.item_table.drop_areas,
                    v['items.frame_type']
        fields = {'items.drop_areas_html'},
                )
         display = h.display_value_factory{},
                tbl
                     :tag('tr')
                        :attr('class', 'upgraded-from-set')
                        :tag('td')
                            :wikitext(
                                v['legacy_variants.removal_version']
                            )
                            :done()
                        :tag('td')
                            :attr('class', 'group legacy-stats plainlist')
                            :wikitext(table.concat(cell, sep))
                            :done()
                    :done()
            end
            tr
                :tag('td')
                    :node(tbl)
        end,
         order = 21001,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'granted_skills'},
         order = 15005,
         header = i18n.item_table.granted_skills,
        args = {'drop', 'drop_monsters'},
         fields = {'items.name'},  
         header = i18n.item_table.drop_monsters,
         fields = {'items.drop_monsters'},
         display = function(tr, data, na, results2)
         display = function(tr, data, na, results2)
             if results2['granted_skills_query'] == nil then  
             if results2['drop_monsters_query'] == nil then
                 results2['granted_skills_query'] = m_cargo.query(
                 results2['drop_monsters_query'] = m_cargo.query(
                     {'items', 'item_mods', 'mods', 'skill', 'items=items2'},
                     {'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
                    tbl[#tbl+1] = m_util.string.format(
                        i18n.item_table.granted_skills_skill_output_format,
                        {
                            level = level,
                            sl = f_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] = 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|link=|%s]]',  
                    },
                    v,
                    {
                     v
                        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',
                     }
                 )
                 )
             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
        end,
             local results = results2['vendor_query'][data['items._pageName']] or {}
        order = 23000,
 
        sort_type = 'text',
             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


data_map.skill_gem_new = {
                tbl[#tbl+1] = string.format(
    {
                    i18n.item_table.vendor_rewards_row_format,
        arg = 'icon',
                    v['vendor_rewards.act'],
        header = i18n.item_table.support_gem_letter,
                    v['vendor_rewards.quest'],
        fields = {'skill_gems.support_gem_letter_html'},
                    v['vendor_rewards.npc'],
        display = h.tbl.display.factory.value{},
                    classes
        order = 1000,
                )
        sort_type = 'text',
            end
    },
 
    {
            local value = table.concat(tbl, '<br>')
        arg = 'skill_icon',
            if value == nil or value == '' then
        header = i18n.item_table.skill_icon,
                tr:node(m_util.html.table_cell('na'))
        fields = {'skill.skill_icon'},
             else
        display = h.tbl.display.factory.value{options = {
                 tr
             [1] = {
                    :tag('td')
                 fmt='[[%s]]',
                        :attr('style', 'text-align:left')
             },
                        :wikitext(value)
        }},
             end
         order = 1001,
         end,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'stat', 'stat_text'},
         order = 18000,
         header = i18n.item_table.stats,
        args = {'price', 'purchase_cost'},
         fields = {'skill.stat_text'},
         header = i18n.item_table.purchase_costs,
         display = h.tbl.display.factory.value{},
         fields = {'item_purchase_costs.name', 'item_purchase_costs.amount'},
        order = 2000,
         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 = {'quality', 'quality_stat_text'},
            if data['item_purchase_costs.name'] ~= nil then
        header = i18n.item_table.quality_stats,
                tbl[#tbl+1] = string.format(
        fields = {'skill.quality_stat_text'},
                        '%sx %s',
        display = h.tbl.display.factory.value{},
                        data['item_purchase_costs.amount'],
         order = 2001,
                        h.item_link{data['item_purchase_costs.name']}
                    )
            end
            h.na_or_val(tr, table.concat(tbl, '<br>'))
         end,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = 'description',
         order = 18001,
         header = i18n.item_table.description,
        args = {'price', 'sell_price'},
         fields = {'skill.description'},
         header = i18n.item_table.sell_price,
         display = h.tbl.display.factory.value{},
         fields = {'item_sell_prices.name', 'item_sell_prices.amount'},
        order = 2100,
         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 = 'level',
                    {
        header = m_game.level_requirement.icon,
                        'item_sell_prices.name',
        fields = h.tbl.range_fields('items.required_level'),
                        'item_sell_prices.amount',
        display = h.tbl.display.factory.range{field='items.required_level'},
                        'item_sell_prices._pageID'
        order = 3004,
                    },
    },
                    {
    {
                        join='items._pageID=item_sell_prices._pageID',
        arg = 'crit',
                        where=string.format(
        header = i18n.item_table.skill_critical_strike_chance,
                            'items._pageID IN (%s) AND item_sell_prices._pageID IS NOT NULL',
        fields = {'skill_levels.critical_strike_chance'},
                            table.concat(results2.pageIDs, ', ')
        display = h.tbl.display.factory.value{options = {
                        ),
            [1] = {
                        orderBy='item_sell_prices.name',
                fmt='%s%%',
                    }
                skill_levels = true,
                )
            },
                results2['sell_price_query'] = m_cargo.map_results_to_id{
        }},
                    results=results2['sell_price_query'],
        order = 4000,
                    field='item_sell_prices._pageID',
        options = {
                }
            [1] = {
            end
                skill_levels = true,
            local results = results2['sell_price_query'][data['items._pageID']] or {}
             },
 
        },
             local tbl = {}
    },
            for _,v in ipairs(results) do
    {
                tbl[#tbl+1] = string.format(
        arg = 'cast_time',
                    '%sx %s',
        header = i18n.item_table.cast_time,
                    v['item_sell_prices.amount'],
        fields = {'skill.cast_time'},
                    h.item_link{v['item_sell_prices.name']}
        display = h.tbl.display.factory.value{options = {
                )
        }},
            end
         order = 4001,
            h.na_or_val(tr, table.concat(tbl, '<br>'))
         options = {
         end,
        },
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'aspd', 'attack_speed', 'attack_speed_multiplier'},
         order = 19000,
         header = i18n.item_table.attack_speed_multiplier,
        args = {'boss', 'boss_name'},
         fields = {'skill_levels.attack_speed_multiplier'},
         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 = 4002,
                        'items._pageName',
        options = {
                        'maps.area_id',
            [1] = {
                        'areas.id',
                skill_levels = true,
                        'areas.boss_monster_ids',
            },
                        'monsters._pageName',
        },
                        'monsters.name',
    },
                        'main_pages._pageName',
    {
                    },
        arg = 'dmgeff',
                    {
        header = i18n.item_table.damage_effectiveness,
                        join=[[
        fields = {'skill_levels.damage_effectiveness'},
                            items._pageID=maps._pageID,
        display = h.tbl.display.factory.value{options = {
                            maps.area_id=areas.id,
            [1] = {
                            areas.boss_monster_ids HOLDS monsters.metadata_id,
                fmt='%s%%',
                            monsters.metadata_id=main_pages.id
                skill_levels = true,
                        ]],
            },
                        where=string.format([[
        }},
                            items._pageID IN (%s)
        order = 4003,
                            AND maps.area_id IS NOT NULL
        options = {
                            AND areas.boss_monster_ids HOLDS LIKE "%%"
             [1] = {
                        ]],
                 skill_levels = true,
                            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 = 'mcm',
         order = 19001,
         header = i18n.item_table.mana_cost_multiplier,
        args = {'boss', 'boss_number'},
         fields = {'skill_levels.mana_multiplier'},
         header = i18n.item_table.boss_number,
         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 = 5000,
                        'items._pageName',
        options = {
                        'maps.area_id',
            [1] = {
                        'areas.id',
                skill_levels = true,
                        'areas.boss_monster_ids',
            },
                        'monsters._pageName',
        },
                        'monsters.name',
    },
                        'main_pages._pageName',
    {
                    },
        arg = 'mana',
                    {
        header = i18n.item_table.mana_cost,
                        join=[[
        fields = {'skill_levels.mana_cost', 'skill.has_percentage_mana_cost', 'skill.has_reservation_mana_cost'},
                            items._pageID=maps._pageID,
        display = function (tr, data, fields, data2)
                            maps.area_id=areas.id,
            local appendix = ''
                            areas.boss_monster_ids HOLDS monsters.metadata_id,
            if m_util.cast.boolean(data['skill.has_percentage_mana_cost']) then
                            monsters.metadata_id=main_pages.id
                appendix = appendix .. '%%'
                        ]],
             end
                        where=string.format([[
             if m_util.cast.boolean(data['skill.has_reservation_mana_cost']) then
                            items._pageID IN (%s)
                 appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
                            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
             end
              
             tr
            h.tbl.display.factory.value{options = {
                 :tag('td')
                 [1] = {
                     :attr('data-sort-value', #tbl)
                     fmt='%d' .. appendix,
                     :wikitext(#tbl)
                     skill_levels = true,
                },
            }}(tr, data, {'skill_levels.mana_cost'}, data2)
         end,
         end,
        order = 5001,
    },
        options = {
    {
            [1] = {
        order = 20000,
                skill_levels = true,
         args = {'legacy'},
            },
         header = i18n.item_table.legacy,
         },
         fields = {'items.name'},
    },
         display = function(tr, data, na, results2)
    {
             if results2['legacy_query'] == nil then
        arg = 'vaal',
                 results2['legacy_query'] = m_cargo.query(
         header = i18n.item_table.vaal_souls_requirement,
                    {'items', 'legacy_variants'},
         fields = {'skill_levels.vaal_souls_requirement'},
                    {
         display = h.tbl.display.factory.value{options = {
                        'items._pageID',
             [1] = {
                        'items._pageName',
                 skill_levels = true,
                        'items.frame_type',
            },
                        'legacy_variants.removal_version',
        }},
                        'legacy_variants.implicit_stat_text',
        order = 6000,
                        'legacy_variants.explicit_stat_text',
        options = {
                        'legacy_variants.stat_text',
            [1] = {
                        'legacy_variants.base_item',
                skill_levels = true,
                        'legacy_variants.required_level'
            },
                    },
        },
                    {
    },
                        join='items._pageID=legacy_variants._pageID',
    {
                        where='legacy_variants.removal_version IS NOT NULL',
        arg = 'vaal',
                        where=string.format(
        header = i18n.item_table.stored_uses,
                            'items._pageID IN (%s) AND legacy_variants.removal_version IS NOT NULL',
        fields = {'skill_levels.vaal_stored_uses'},
                            table.concat(results2.pageIDs, ', ')
        display = h.tbl.display.factory.value{options = {
                        ),
            [1] = {
                        orderBy='items._pageName',
                skill_levels = true,
                    }
            },
                )
        }},
 
        order = 6001,
                results2['legacy_query'] = m_cargo.map_results_to_id{
        options = {
                    results=results2['legacy_query'],
            [1] = {
                    field='items._pageName',
                skill_levels = true,
                }
            },
            end
        },
            local results = results2['legacy_query'][data['items._pageName']] or {}
    },
 
    {
            local tbl = mw.html.create('table')
        arg = 'radius',
                :attr('width', '100%')
        header = i18n.item_table.primary_radius,
            for _, v in ipairs(results) do
        fields = {'skill.radius', 'skill.radius_description'},
                local cell = {}
        options = {[2] = {optional = true}},
                local l = {
        display = function (tr, data)
                    'legacy_variants.base_item',
            tr
                    'legacy_variants.stat_text'
                :tag('td')
                }
                     :attr('data-sort-value', data['skill.radius'])
 
                     :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_description'}(nil, nil, data['skill.radius']))
                -- Clean up data:
        end,
                for _, k in ipairs(l) do
        order = 7000,
                    if v[k] ~= nil then
    },
                        local s = m_util.string.split(v[k], '*')
    {
                        local s_flt = {}
        arg = 'radius',
                        for _, sss in ipairs(s) do
        header = i18n.item_table.secondary_radius,
                            if sss ~= nil and sss ~= '' then
        fields = {'skill.radius_secondary', 'skill.radius_secondary_description'},
                                s_flt[#s_flt+1] = string.gsub(sss, '\n', '')
        options = {[2] = {optional = true}},
                            end
        display = function (tr, data)
                        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
             tr
                 :tag('td')
                 :tag('td')
                     :attr('data-sort-value', data['skill.radius_secondary'])
                     :node(tbl)
                    :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_secondary_description'}(nil, nil, data['skill.radius_secondary']))
         end,
         end,
         order = 7001,
         sort_type = 'text',
     },
     },
     {
     {
         arg = 'radius',
         order = 21000,
         header = i18n.item_table.tertiary_radius,
        args = {'granted_skills'},
         fields = {'skill.radius_tertiary', 'skill.radius_tertiary_description'},
         header = i18n.item_table.granted_skills,
        options = {[2] = {optional = true}},
         fields = {'items.name'},
         display = function (tr, data)
         display = function(tr, data, na, results2)
             tr
             if results2['granted_skills_query'] == nil then
                 :tag('td')
                 results2['granted_skills_query'] = m_cargo.query(
                     :attr('data-sort-value', data['skill.radius_tertiary'])
                     {'items', 'item_mods', 'mods', 'skill', 'items=items2'},
                  :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_tertiary_description'}(nil, nil, data['skill.radius_tertiary']))
                    {
        end,
                        'items._pageName',
        order = 7002,
                        'items.name',
    },
                        'item_mods.id',
}
                        'mods._pageName',
 
                        'mods.id',
for i, attr in ipairs(m_game.constants.attribute_order) do
                        'mods.granted_skill',
    local attr_data = m_game.constants.attributes[attr]
                        'mods.stat_text_raw',
    table.insert(data_map.generic_item, 7, {
                        'skill._pageName',
        arg = attr_data.arg,
                        'skill.skill_id',
        header = attr_data.icon,
                        'skill.active_skill_name',
        fields = h.tbl.range_fields(string.format('items.required_%s', attr)),
                        'skill.skill_icon',
        display = h.tbl.display.factory.range{field=string.format('items.required_%s', attr)},
                        'skill.stat_text',
        order = 5000+i,
                        'items2.class',
    })
                        'items2.name',
    table.insert(data_map.skill_gem_new, 1, {
                        'items2.inventory_icon',
        arg = attr_data.arg,
                        'items2.size_x',
        header = attr_data.icon,
                        'items2.size_y',
        fields = {string.format('skill_gems.%s_percent', attr)},
                        'items2.html',
        display = function (tr, data)
                    },
            tr
                    {
                :tag('td')
                        join='items._pageID=item_mods._pageID, item_mods.id=mods.id, mods.granted_skill=skill.skill_id, skill._pageID=items2._pageID',
                    :attr('data-sort-value', data[string.format('skill_gems.%s_percent', attr)])
                        where=string.format(
                    :wikitext('[[File:Yes.png|yes|link=]]')
                            'items._pageID IN (%s) AND mods.granted_skill IS NOT NULL',
        end,
                            table.concat(results2.pageIDs, ', ')
        order = 3000+i,
                        ),
    })
                    }
end
                )


                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 = {}
-- Invoke callables
            for _, v in ipairs(results) do
-- ----------------------------------------------------------------------------


local p = {}
                -- 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
-- Template:Item table
                -- 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


function p.item_table(frame)
                -- Use different formats depending on if it's a gem or
    --[[
                -- not:
    Creates a generic table for items.
                if v['items2.class'] == nil  then
   
                    tbl[#tbl+1] = h.string.format(
    Examples
                        i18n.item_table.granted_skills_skill_output_format,
    --------
                        {
    = p.item_table{
                            level = level,
        q_tables = 'items, vendor_rewards',
                            sl = h.skill_link{
        q_join = 'items.name = vendor_rewards.reward',
                                skip_query=true,
        q_where= 'vendor_rewards.reward IS NOT NULL AND (items.class = "Active Skill Gems" OR items.class = "Support Skill Gems")',
                                page = v['skill.active_skill_name']
        vendor=1,
                                    or v['skill._pageName']
    }
                                    or v['mods._pageName']
   
                                    or '',
    ]]
                                name = v['skill.active_skill_name']
   
                                    or v['skill.stat_text']
    local t = os.clock()
                                    or v['mods.granted_skill'],
    -- args
                                icon = v['skill.skill_icon'],
    local tpl_args = getArgs(frame, {
                            },
            parentFirst = true
                        }
        })
                    )
    frame = m_util.misc.get_frame(frame)
                else
   
                    local il_args = {
    tpl_args.q_where = m_cargo.replace_holds{string=tpl_args.q_where}
                        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'],
                    }


    local modes = {
                    -- TODO: add in tpl_args.
        skill = {
                    if no_html == nil then
            data = data_map.skill_gem_new,
                        il_args.html = v['items2.html']
            header = i18n.item_table.skill_gem,
                    end
        },
                    tbl[#tbl+1] = h.string.format(
        item = {
                        i18n.item_table.granted_skills_gem_output_format,
            data = data_map.generic_item,
                        {
            header = i18n.item_table.item,
                            level = level,
        },
                            il = h.item_link(il_args),
    }
                        }
   
                    )
    if tpl_args.mode == nil then
                end
        tpl_args.mode = 'item'
            end
    end
            h.na_or_val(tr, table.concat(tbl, '<br>'))
   
        end,
    if modes[tpl_args.mode] == nil then
        sort_type = 'text',
        error(i18n.errors.invalid_item_table_mode)
     },
     end
     {
      
        order = 23000,
    local results2 = {
         args = {'alternate_art'},
         stats = {},
         header = i18n.item_table.alternate_art,
         skill_levels = {},
         fields = {'items.alternate_art_inventory_icons'},
         pageIDs = {},
        display = function (tr, data)
    }
            local alt_art = m_util.string.split(
   
                data['items.alternate_art_inventory_icons'],
    local row_infos = {}
                ','
    for _, row_info in ipairs(modes[tpl_args.mode].data) do
            )
        local enabled = false
 
        if row_info.arg == nil then
            -- TODO: Use il instead to handle size?
            enabled = true
             -- local size = 39
        elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then
            local out = {}
             enabled = true
             for i,v in ipairs(alt_art) do
        elseif type(row_info.arg) == 'table' then
                 out[#out+1] = string.format(
             for _, argument in ipairs(row_info.arg) do
                    '[[%s|link=|%s]]',
                 if m_util.cast.boolean(tpl_args[argument]) then
                     v,
                     enabled = true
                     v
                     break
                 )
                 end
             end
             end
         end
 
          
            tr
        if enabled then
                :tag('td')
            row_info.options = row_info.options or {}
                    :wikitext(table.concat(out, ''))
            row_infos[#row_infos+1] = row_info
         end,
         end
         sort_type = 'text',
    end
    },
 
}
    -- Parse stat arguments
 
    local stat_columns = {}
data_map.skill_gem = {
    local query_stats = {}
    {
     local i = 0
        order = 1000,
     repeat
         args = {'gem_tier'},
         i = i + 1
        header = i18n.item_table.tier,
          
        fields = {'skill_gems.gem_tier'},
         local prefix = string.format('stat_column%s_', i)
        display = h.display_value_factory{},
         local col_info = {
     },
             header = tpl_args[prefix .. 'header'] or tostring(i),
     {
             format = tpl_args[prefix .. 'format'],
         order = 1001,
            stat_format = tpl_args[prefix .. 'stat_format'] or 'separate',
         args = {'skill_icon'},
            order = tonumber(tpl_args[prefix .. 'order']) or (10000000 + i),
         header = i18n.item_table.skill_icon,
            stats = {},
        fields = {'skill.skill_icon'},
            options = {},
         display = h.display_value_factory{
         }
             fmt_options = {
          
                [1] = {
         local j = 0
                    fmt = '[[%s]]',
         repeat
                },
            j = j +1
             },
          
        },
            local stat_info = {
        sort_type = 'text',
                id = tpl_args[string.format('%sstat%s_id', prefix, j)],
    },
             }
    {
           
        order = 2000,
            if stat_info.id then
        args = {'stat', 'stat_text'},
                col_info.stats[#col_info.stats+1] = stat_info
        header = i18n.item_table.stats,
                query_stats[stat_info.id] = {}  
        fields = {'skill.stat_text'},
             else
        display = h.display_value_factory{},
                -- Stop iteration entirely if this was the first index but no stat was supplied. We assume that we stop in this case.
        sort_type = 'text',
                 if j == 1 then
    },
                    i = nil
    {
                 end
        order = 2001,
                 -- stop iteration
        args = {'quality', 'quality_stat_text'},
                j = nil
        header = i18n.item_table.quality_stats,
             end
        fields = {'skill.quality_stat_text'},
         until j == nil
        display = h.display_value_factory{},
          
        sort_type = 'text',
         -- Don't add this column if no stats were provided.  
    },
        if #col_info.stats > 0 then
    {
             stat_columns[#stat_columns+1] = col_info
        order = 2100,
         end
         args = {'description'},
    until i == nil
         header = i18n.item_table.description,
 
         fields = {'skill.description'},
     for _, col_info in ipairs(stat_columns) do
         display = h.display_value_factory{},
        local row_info = {
        sort_type = 'text',
            --arg
    },
            header = col_info.header,
    {
            fields = {},
         order = 3000,
             display = function(tr, data, properties)
        args = {'level'},
                 if col_info.stat_format == 'separate' then
        header = m_util.html.tooltip(
                    local stat_texts = {}
            string.format(
                    local num_stats = 0
                '[[%s|link=|alt=%s]]',
                    local vmax = 0
                i18n.item_table.level_icon,
                    for _, stat_info in ipairs(col_info.stats) do
                i18n.item_table.gem_level_requirement
                        num_stats = num_stats + 1
            ),
                        -- stat results from outside body
             i18n.item_table.gem_level_requirement
                        local stat = (results2.stats[data['items._pageName']] or {})[stat_info.id]
        ),
                        if stat ~= nil then
        fields = h.range_fields_factory{fields={'items.required_level'}},
                            stat_texts[#stat_texts+1] = m_util.html.format_value(tpl_args, frame, stat, {no_color=true})
        display = h.display_range_factory{field='items.required_level'},
                            vmax = vmax + stat.max
    },
                        end
    {
                    end
        order = 3001,
                   
        args = {'str'},
                    if num_stats ~= #stat_texts then
        header = m_util.html.tooltip(
                        tr:wikitext(m_util.html.td.na())
             string.format(
                    else
                 '[[%s|link=|alt=%s]]',
                        local text
                 i18n.item_table.str_icon,
                        if col_info.format then
                 i18n.item_table.str_gem
                            text = string.format(col_info.format, unpack(stat_texts))
            ),
                        else
             i18n.item_table.str_gem
                            text = table.concat(stat_texts, ', ')
         ),
                        end
         fields = {'skill_gems.strength_percent'},
                   
         display = h.display_yesno_factory{
                        tr:tag('td')
            field = 'skill_gems.strength_percent',
                            :attr('data-sort-value', vmax)
             condition = h.value_greater_than_zero,
                            :attr('class', 'tc -mod')
         },
                            :wikitext(text)
        sort_type = 'text',
                    end
     },
                elseif col_info.stat_format == 'add' then
    {
                    local total_stat = {
        order = 3002,
                        min = 0,
        args = {'dex'},
                        max = 0,
        header = m_util.html.tooltip(
                        avg = 0,
             string.format(
                    }
                 '[[%s|link=|alt=%s]]',
                    for _, stat_info in ipairs(col_info.stats) do
                i18n.item_table.dex_icon,
                        local stat = (results2.stats[data['items._pageName']] or {})[stat_info.id]
                i18n.item_table.dex_gem
                        if stat ~= nil then
            ),
                            for k, v in pairs(total_stat) do
            i18n.item_table.dex_gem
                                total_stat[k] = v + stat[k]
        ),
                            end
        fields = {'skill_gems.dexterity_percent'},
                        end
        display = h.display_yesno_factory{
                    end
            field = 'skill_gems.dexterity_percent',
                   
            condition = h.value_greater_than_zero,
                    if col_info.format == nil then
        },
                        col_info.format = '%s'
        sort_type = 'text',
                    end
    },
                   
    {
                    tr:tag('td')
        order = 3003,
                        :attr('data-sort-value', total_stat.max)
        args = {'int'},
                        :attr('class', 'tc -mod')
        header = m_util.html.tooltip(
                        :wikitext(string.format(col_info.format, m_util.html.format_value(tpl_args, frame, total_stat, {no_color=true})))
            string.format(
                else
                '[[%s|link=|alt=%s]]',
                     error(string.format(i18n.errors.generic_argument_parameter, 'stat_format', col_info.stat_format))
                i18n.item_table.int_icon,
                end
                i18n.item_table.int_gem
             end,
            ),
             order = col_info.order,
            i18n.item_table.int_gem
         }
        ),
        table.insert(row_infos, row_info)
        fields = {'skill_gems.intelligence_percent'},
     end
        display = h.display_yesno_factory{
          
            field = 'skill_gems.intelligence_percent',
    -- sort the rows
            condition = h.value_greater_than_zero,
    table.sort(row_infos, function (a, b)
        },
         return (a.order or 0) < (b.order or 0)
        sort_type = 'text',
    end)
    },
   
    {
    -- Parse query arguments
        order = 4000,
    local tables_assoc = {items=true}
        args = {'crit'},
     local fields = {
        header = i18n.item_table.skill_critical_strike_chance,
         'items._pageID',
        fields = {'skill_levels.critical_strike_chance'},
         'items._pageName',
        display = h.display_value_factory{
        'items.name',
            fmt_options = {
         'items.inventory_icon',
                [1] = {
         'items.html',
                    fmt = '%s%%',
         'items.size_x',
                },
        'items.size_y',
            },
    }
        },
   
        field_options = {
    --
            [1] = {
    local prepend = {
                skill_levels = true,
        q_groupBy=true,
            },
         q_tables=true,
        },
     }
    },
      
    {
    local query = {}
        order = 4001,
    for key, value in pairs(tpl_args) do
        args = {'cast_time'},
        if string.sub(key, 0, 2) == 'q_' then
        header = i18n.item_table.cast_time,
             if prepend[key] then
        fields = {'skill.cast_time'},
                 value = ',' .. value
        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
             end
              
             h.display_value_factory{
            query[string.sub(key, 3)] = value
                fmt_options = {
        end
                    [1] = {
    end
                        fmt = '%d' .. appendix,
   
                    },
    local skill_levels = {}
                },
    for _, rowinfo in ipairs(row_infos) do
             }(tr, data, {cost_field}, data2)
        if type(rowinfo.fields) == 'function' then
         end,
             rowinfo.fields = rowinfo.fields()
         -- Need one set of options per field.
         end
        field_options = {
         for index, field in ipairs(rowinfo.fields) do
             [1] = {
            rowinfo.options[index] = rowinfo.options[index] or {}
                 skill_levels = true,
             if rowinfo.options[index].skill_levels then
             },
                 skill_levels[#skill_levels+1] = field
            [2] = {
             else
                 skill_levels = true,
                fields[#fields+1] = field
             },
                 tables_assoc[m_util.string.split(field, '%.')[1]] = true
            [3] = {
             end
                skill_levels = true,
        end
            },
    end
        },
 
     },
    if #skill_levels > 0 then
     {
        fields[#fields+1] = 'skill.max_level'
        order = 6000,
        tables_assoc.skill = true
         args = {'vaal'},
   
        header = i18n.item_table.vaal_souls_requirement,
    end
         fields = {'skill_levels.vaal_souls_requirement'},
      
        display = h.display_value_factory{},
     -- Reformat the tables and fields so they can be retrieved correctly:   
         field_options = {
    local tables = {}
             [1] = {
    for table_name,_ in pairs(tables_assoc) do
                skill_levels = true,
         tables[#tables+1] = table_name
            },
    end
         },
    local tbls = table.concat(tables,',') .. (query.tables or '')
     },
    query.tables = m_util.string.split(tbls, ',')
     {
   
         order = 6001,
    for index, field in ipairs(fields) do
        args = {'vaal'},
         fields[index] = string.format('%s=%s', field, field)
        header = i18n.item_table.stored_uses,
    end
         fields = {'skill_levels.vaal_stored_uses'},
    query.fields = fields
        display = h.display_value_factory{},
   
         field_options = {
    -- Take care of the minimum required joins, joins from templates
            [1] = {
    -- must still be userdefined:
                skill_levels = true,
    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
         order = 7000,
     end
         args = {'radius'},
     if #joins > 0 and query.join then
        header = i18n.item_table.primary_radius,
         query.join = table.concat(joins, ',') .. ',' .. query.join
        fields = {'skill.radius', 'skill.radius_description'},
    elseif #joins > 0 and not query.join then
         field_options = {
         query.join = table.concat(joins, ',')
             [2] = {
    elseif #joins == 0 and query.join then
                optional = true,
         -- leave query.join as is
             },
    end
        },
   
        display = function (tr, data)
    -- Needed to eliminate duplicates supplied via table joins:
            tr
    query.groupBy = 'items._pageID' .. (query.groupBy or '')
                 :tag('td')
   
                    :attr('data-sort-value', data['skill.radius'])
    -- Query results:
                    :wikitext(h.display_descriptor_value_factory{tbl=data, key='skill.radius_description'}(data['skill.radius']))
    local results = m_cargo.query(query.tables, query.fields, query)
        end,
 
    },
    if #results == 0 and tpl_args.default ~= nil then
    {
         return tpl_args.default
        order = 7001,
     end
        args = {'radius'},
      
        header = i18n.item_table.secondary_radius,
    if #results > 0 then
        fields = {'skill.radius_secondary', 'skill.radius_secondary_description'},
         -- Create a list of found pageIDs for column specific queries:
        field_options = {
         for _,v in ipairs(results) do
             [2] = {
            results2.pageIDs[#results2.pageIDs+1] = v['items._pageID']
                optional = true,
        end
             },
       
        },
        -- fetch skill level information
        display = function (tr, data)
         if #skill_levels > 0 then
            tr
             skill_levels[#skill_levels+1] = 'skill_levels._pageName'
                 :tag('td')
            skill_levels[#skill_levels+1] = 'skill_levels.level'
                    :attr('data-sort-value', data['skill.radius_secondary'])
             local pages = {}
                    :wikitext(h.display_descriptor_value_factory{tbl=data, key='skill.radius_secondary_description'}(data['skill.radius_secondary']))
            for _, row in ipairs(results) do
        end,
                 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(
         order = 7002,
                {'skill_levels'},
        args = {'radius'},
                skill_levels,
        header = i18n.item_table.tertiary_radius,
                {
        fields = {'skill.radius_tertiary', 'skill.radius_tertiary_description'},
                    where=table.concat(pages, ' OR '),
        field_options = {
                    groupBy='skill_levels._pageID, skill_levels.level',
             [2] = {
                }
                optional = true,
             )
             },
             -- map to results
         },
            for _, row in ipairs(temp) do
        display = function (tr, data)
                 if results2.skill_levels[row['skill_levels._pageName']] == nil then
             tr
                  results2.skill_levels[row['skill_levels._pageName']] = {}
                 :tag('td')
                end
                    :attr('data-sort-value', data['skill.radius_tertiary'])
                -- TODO: convert to int?
                  :wikitext(h.display_descriptor_value_factory{tbl=data, key='skill.radius_tertiary_description'}(data['skill.radius_tertiary']))
                results2.skill_levels[row['skill_levels._pageName']][row['skill_levels.level']] = row
        end,
            end
    },
        end
    {
      
        order = 8000,
         if #stat_columns > 0 then
        args = {'drop', 'drop_text'},
            local pages = {}
        header = i18n.item_table.drop_text,
            for _, row in ipairs(results) do
        fields = {'items.drop_text'},
                pages[#pages+1] = string.format('item_stats._pageID="%s"', row['items._pageID'])
        display = h.display_value_factory{},
            end
        sort_type = 'text',
           
    },
            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
-- ----------------------------------------------------------------------------
                local stat = {
-- Main functions
                    min = tonumber(row['item_stats.min']),
-- ----------------------------------------------------------------------------
                    max = tonumber(row['item_stats.max']),
 
                    avg = tonumber(row['item_stats.avg']),
local function _item_table(args)
                }
    --[[
               
    Creates a generic table for items.
                if results2.stats[row['item_stats._pageName']] == nil then
 
                    results2.stats[row['item_stats._pageName']] = {[row['item_stats.id']] = stat}
    Examples
                else
    --------
                    results2.stats[row['item_stats._pageName']][row['item_stats.id']] = stat
    = p.item_table{
                end
        tables='vendor_rewards, quest_rewards, skill_gems',
            end
        join='items._pageID=vendor_rewards._pageID, items._pageID=quest_rewards._pageID, items._pageID=skill_gems._pageID',
         end
        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)',
    end
         vendor=1,
      
     }
      
     ]]
    --
 
     -- Display the table
     m_item_util = m_item_util or (use_sandbox and require('Module:Item util/sandbox') or require('Module:Item util'))
    --
 
   
     local t = os.clock()
     local tbl = mw.html.create('table')
 
     tbl:attr('class', 'wikitable sortable item-table')
     args.mode = args.mode or 'item'
      
     local modes = {
    -- Headers:
        skill = {
    local tr = tbl:tag('tr')
            data = data_map.skill_gem,
    tr
            header = i18n.item_table.skill_gem,
         :tag('th')
        },
             :wikitext(modes[tpl_args.mode].header)
         item = {
            :done()
            data = data_map.generic_item,
           
             header = i18n.item_table.item,
     for _, row_info in ipairs(row_infos) do
        },
         tr
    }
            :tag('th')
    if modes[args.mode] == nil then
                :attr('data-sort-type', row_info.sort_type or 'number')
        error(i18n.errors.invalid_item_table_mode)
                :wikitext(row_info.header)
    end
                :done()
 
     -- 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
     end
   
 
     -- Rows:
     local results2 = {
    for _, row in ipairs(results) do
        stats = {},
         tr = tbl:tag('tr')
         skill_levels = {},
       
         pageIDs = {},
         local il_args = {
    }
            skip_query=true,
 
            page=row['items._pageName'],
    local row_infos = {}
            name=row['items.name'],
    for _, row_info in ipairs(modes[args.mode].data) do
            inventory_icon=row['items.inventory_icon'],  
         local enabled = false
            width=row['items.size_x'],
         if type(row_info.args) == 'table' then
            height=row['items.size_y'],
             for _, a in ipairs(row_info.args) do
        }
                 if m_util.cast.boolean(args[a]) then
          
                     enabled = true
         if tpl_args.no_html == nil then
                     break
            il_args.html = row['items.html']
        end
       
        if tpl_args.large then
             il_args.large = tpl_args.large
        end
       
        tr
            :tag('td')
                :wikitext(f_item_link(il_args))
                :done()
               
        for _, rowinfo in ipairs(row_infos) do
            -- this has been cast from a function in an earlier step
            local display = true
            for index, field in ipairs(rowinfo.fields) do
                -- this will bet set to an empty value not nil confusingly
                 if row[field] == 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
             end
             if display then
        else
                rowinfo.display(tr, row, rowinfo.fields, results2)
             enabled = true
             else
        end
                tr:wikitext(m_util.html.td.na())
        if enabled then
             end
             row_info.field_options = row_info.field_options or {}
             row_infos[#row_infos+1] = row_info
         end
         end
     end
     end


     cats = {}
     -- Parse stat arguments
     if #results == query.limit then
    local stat_columns = {}
         cats[#cats+1] = i18n.categories.query_limit
     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
     end
   
    if #results == 0 then
        cats[#cats+1] = i18n.categories.no_results
    end
   
    mw.logObject({os.clock() - t, query})
   
    return tostring(tbl) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
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
-- Map item drops
                        tr:node(m_util.html.table_cell('na'))
-------------------------------------------------------------------------------
                    else
 
                        local text
function p.map_item_drops(frame)
                        if col_info.format then
    --[[
                            text = string.format(col_info.format, unpack(stat_texts))
    Gets the area id from the map item and activates
                        else
    Template:Area_item_drops.
                            text = table.concat(stat_texts, ', ')
   
                        end
    Examples:
    = p.map_item_drops{page='Underground River Map (War for the Atlas)'}
    ]]
   
    -- Get args
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
   
    tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
   
    local results = m_cargo.query(
        {'maps'},
        {'maps.area_id'},
        {
            where=string.format('maps._pageName="%s" AND maps.area_id IS NOT NULL', tpl_args.page),
            -- Only need each page name once
            groupBy='maps._pageName',
        }
    )
    local id = ''
    if #results > 0 then
        id = results[1]['maps.area_id']
    end
    return frame:expandTemplate{ title = 'Area item drops', args = {area_id=id} }
end


-------------------------------------------------------------------------------
                        tr:tag('td')
-- Prophecy description
                            :attr('data-sort-value', vmax)
-------------------------------------------------------------------------------
                            :attr('class', 'tc -mod')
 
                            :wikitext(text)
function p.prophecy_description(frame)
                    end
    -- Get args
                elseif col_info.stat_format == 'add' then
    local tpl_args = getArgs(frame, {
                    local total_stat = {
        parentFirst = true
                        min = 0,
    })
                        max = 0,
    frame = m_util.misc.get_frame(frame)
                        avg = 0,
   
                    }
    tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
                    for _, stat_info in ipairs(col_info.stats) do
   
                        local stat = (results2.stats[data['items._pageName']] or {})[stat_info.id]
    local results = m_cargo.query(
                        if stat ~= nil then
        {'prophecies'},
                            for k, v in pairs(total_stat) do
        {'prophecies.objective', 'prophecies.reward'},
                                total_stat[k] = v + stat[k]
        {
                            end
            where=string.format('prophecies._pageName="%s"', tpl_args.page),
                        end
            -- Only need each page name once
                    end
            groupBy='prophecies._pageName',
 
        }
                    if col_info.format == nil then
    )
                        col_info.format = '%s'
   
                    end
    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


-- ----------------------------------------------------------------------------
                    tr:tag('td')
-- Item disambiguation
                        :attr('data-sort-value', total_stat.max)
-- ----------------------------------------------------------------------------
                        :attr('class', 'tc -mod')
function h.find_aliases(tpl_args)
                        :wikitext(string.format(col_info.format, m_util.html.format_value(args, total_stat, {no_color=true})))
  --[[
                else
  This function queries items for an item name, then checks if it has
                    error(string.format(i18n.errors.generic_argument_parameter, 'stat_format', col_info.stat_format))
  had any name changes then queries for that name as well.
                end
  ]]
            end,
   
            order = col_info.order,
     -- Get initial name:
        }
     tpl_args.name_list = {
        table.insert(row_infos, row_info)
         tpl_args.name or m_util.string.split(
    end
            tostring(mw.title.getCurrentTitle()),  
 
            ' %('
    -- 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,
     }
     }
      
 
     -- Query for items with similar name, repeat until no new names are
     -- Namespace condition
     -- found.
     -- This is mainly to prevent items from user pages or other testing pages
     local n
     -- from being returned in the query results.
    local results = {}
     if args.namespaces ~= 'any' then
    local hash = {}
         local namespaces = m_util.cast.table(args.namespaces, {callback=m_util.cast.number})
    repeat
         if #namespaces > 0 then
         local n_old = #tpl_args.name_list
            namespaces = table.concat(namespaces, ',')
       
        else
        -- Multiple HOLDS doesn't work. Using __FULL and REGEXP instead.
             namespaces = m_item_util.get_item_namespaces{format = 'list'}
        local where_tbl = {}
        end
         for _, item_name in ipairs(tpl_args.name_list) do
        query.where = string.format('(%s) AND items._pageNamespace IN (%s)', query.where, namespaces)
             for _, prefix in ipairs({'', 'Shaped '}) do
    end
                where_tbl[#where_tbl+1] = string.format(
 
                    '(items.name_list__FULL REGEXP "(�|^)%s%s(�|$)")',
    -- Minimum required tables and fields, based on display options
                    prefix,
    local skill_levels = {}
                    item_name
    for _, rowinfo in ipairs(row_infos) do
                )
        if type(rowinfo.fields) == 'function' then
             end
             rowinfo.fields = rowinfo.fields()
         end
         end
         local where_str = table.concat(where_tbl, ' OR ')
         for index, field in ipairs(rowinfo.fields) do
       
            rowinfo.field_options[index] = rowinfo.field_options[index] or {}
        results = m_cargo.query(
             if rowinfo.field_options[index].skill_levels then
            {'items', 'maps'},
                 skill_levels[#skill_levels+1] = field
             {
             else
                'items._pageName',
                 fields[#fields+1] = field
                'items.name',
                 tables[#tables+1] = m_util.string.split(field, '.', true)[1]
                 'items.name_list',
                '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
           
            for j,m in ipairs(r) do
                if hash[m] == nil then
                    hash[m] = m
                    tpl_args.name_list[#tpl_args.name_list+1] = m
                end
             end
             end
         end
         end
     until #tpl_args.name_list == n_old
     end
      
    if #skill_levels > 0 then
     return results
        fields[#fields+1] = 'skill.max_level'
end
        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


function p.item_disambiguation(frame)
     if #results > 0 then
    --[[
         -- Create a list of found pageIDs for column specific queries:
    This function finds that items with a name or has had that name.
        for _,v in ipairs(results) do
   
             results2.pageIDs[#results2.pageIDs+1] = v['items._pageID']
    To do
    -----
     Should text imply which is the original map, even if it isn't (Original)?
    How to properly sort drop disabled items, with removal version?
    How to deal with names that have been used multiple times? Terrace Map
   
    Examples
    --------
    = p.item_disambiguation{name='Abyss Map'}
    = p.item_disambiguation{name='Caldera Map'}
    = p.item_disambiguation{name='Crypt Map'}
    = p.item_disambiguation{name='Catacombs Map'}
    = p.item_disambiguation{name='Harbinger Map (High Tier)'}
    ]]
   
    -- Get template arguments.
    local tpl_args = getArgs(frame, {
         parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
   
    local current_title = tostring(mw.title.getCurrentTitle())
    -- 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
           
           
            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:
    local old_args = {
        'war',
        'atlas',
        'awakening',
        'original',
        'heading',
        '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
   
    return table.concat(out, '')
end


 
        -- fetch skill level information
function p.simple_item_list(frame)
        if #skill_levels > 0 then
    --[[
            skill_levels[#skill_levels+1] = 'skill_levels._pageName'
    Creates a simple list of items.
            skill_levels[#skill_levels+1] = 'skill_levels.level'
   
            local pages = {}
    Examples
            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'])
    = p.simple_item_list{
            end
        q_tables='maps',
            local temp = m_cargo.query(
        q_join='items._pageID=maps._pageID',  
                {'skill_levels'},
        q_where='maps.tier=1 AND items.drop_enabled=1 AND items.rarity_id="normal"',
                skill_levels,
        no_html=1,
                {
        link_from_name=1,
                    where=table.concat(pages, ' OR '),
    }
                    groupBy='skill_levels._pageID, skill_levels.level',
    ]]
                }
   
            )
    -- Args
            -- map to results
    local tpl_args = getArgs(frame, {
            for _, row in ipairs(temp) do
        parentFirst = true
                if results2.skill_levels[row['skill_levels._pageName']] == nil then
    })
                  results2.skill_levels[row['skill_levels._pageName']] = {}
    frame = m_util.misc.get_frame(frame)
                end
   
                -- TODO: convert to int?
    local query = {}
                results2.skill_levels[row['skill_levels._pageName']][row['skill_levels.level']] = row
    for key, value in pairs(tpl_args) do  
            end
        if string.sub(key, 0, 2) == 'q_' then
        end
            query[string.sub(key, 3)] = value
 
        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
     end
     end
      
 
     local fields = {
    --
        'items._pageName',
    -- Display the table
        'items.name',
     --
        'items.class',
 
    }
     local tbl = mw.html.create('table')
   
    tbl:attr('class', 'wikitable sortable item-table')
     if tpl_args.no_icon == nil then
     if m_util.cast.boolean(args.responsive) then
        fields[#fields+1] = 'items.inventory_icon'
         tbl:addClass('responsive-table')
    end
   
    if tpl_args.no_html == nil then
         fields[#fields+1] = 'items.html'
     end
     end
      
 
     local tables = m_util.string.split(tpl_args.q_tables or '', ',%s*')
     -- Headers:
    table.insert(tables, 'items')
     local tr = tbl:tag('tr')
      
    tr
    query.groupBy = query.groupBy or 'items._pageID'
        :tag('th')
   
            :wikitext(modes[args.mode].header)
    local results = m_cargo.query(
            :done()
         tables,
     for _, row_info in ipairs(row_infos) do
         fields,
        local th = tr:tag('th')
        query
 
     )
        if row_info.colspan then
       
            th:attr('colspan', row_info.colspan)
     local out = {}
         end
 
         th
            :attr('data-sort-type', row_info.sort_type or 'number')
            :wikitext(row_info.header)
     end
 
     -- Rows:
     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')
         return table.concat(out, '\n')
     elseif tpl_args.format == 'none' then
     elseif args.format == 'none' then
         return table.concat(out, '\n')
         return table.concat(out, '\n')
     elseif tpl_args.format == 'li' then
     elseif args.format == 'li' then
         return table.concat(out)
         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
     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,411: 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

Latest revision as of 21:42, 16 July 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')

-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Item table')

local m_cargo = use_sandbox and require('Module:Cargo/sandbox') or require('Module:Cargo')
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 = use_sandbox and require('Module:Item link/sandbox').item_link or 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 = use_sandbox and require('Module:Skill link/sandbox').skill_link or 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.tooltip(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.tooltip(
            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.tooltip(
            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.tooltip(
            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.tooltip(
            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'},
    },
    {
        order = 1611,
        args = {'reload_time'},
        header = i18n.item_table.reload_time,
        fields = h.range_fields_factory{fields={'weapons.reload_time'}},
        display = h.display_range_factory{field='weapons.reload_time'},
    },
    -- 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.tooltip(
            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.tooltip(
            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.tooltip(
            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.tooltip(
            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,
    },
    {
        order = 8000,
        args = {'drop', 'drop_text'},
        header = i18n.item_table.drop_text,
        fields = {'items.drop_text'},
        display = h.display_value_factory{},
        sort_type = 'text',
    },
}

-- ----------------------------------------------------------------------------
-- Main functions
-- ----------------------------------------------------------------------------

local function _item_table(args)
    --[[
    Creates a generic table for items.

    Examples
    --------
    = p.item_table{
        tables='vendor_rewards, quest_rewards, skill_gems',
        join='items._pageID=vendor_rewards._pageID, items._pageID=quest_rewards._pageID, items._pageID=skill_gems._pageID',
        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 (use_sandbox and require('Module:Item util/sandbox') 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

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