Module:Item table: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
>OmegaK2
No edit summary
>Illviljan
(Add rarity and rarity_id. And remove a bunch of whitespaces.)
Line 1: Line 1:
-- Item table
-- Item table
--  
--
-- Creates various item tables from cargo queries.  
-- Creates various item tables from cargo queries.
--  
--
--  
--
-- Todo list
-- Todo list
-- ---------
-- ---------
-- * Handle table columns that can have multiple cargo rows, preferably  
-- * Handle table columns that can have multiple cargo rows, preferably
--  in one or two queries. Not per column AND row.
--  in one or two queries. Not per column AND row.
-- * Handle template include size? Remove item link when getting close  
-- * Handle template include size? Remove item link when getting close
--  to the limit?  
--  to the limit?
-- * Add a wrapper around f_item_link to be able to disable all  
-- * Add a wrapper around f_item_link to be able to disable all
--  hoverboxes, to avoid running into template expansion size issues.
--  hoverboxes, to avoid running into template expansion size issues.


Line 64: Line 64:
         metadata_id = 'Metadata ID',
         metadata_id = 'Metadata ID',
         item_class = 'Item Class',
         item_class = 'Item Class',
        rarity = 'Rarity',
        rarity_id = 'Rarity ID',
         essence_level = 'Essence<br>Level',
         essence_level = 'Essence<br>Level',
         drop_level = 'Drop<br>Level',
         drop_level = 'Drop<br>Level',
Line 107: Line 109:
         prediction_text = 'Prediction',
         prediction_text = 'Prediction',
         help_text = 'Help Text',
         help_text = 'Help Text',
         seal_cost = m_util.html.abbr('Seal<br>Cost', 'Silver Coin cost of sealing this prophecies into an item'),  
         seal_cost = m_util.html.abbr('Seal<br>Cost', 'Silver Coin cost of sealing this prophecies into an item'),
         objective = 'Objective',
         objective = 'Objective',
         reward = 'Reward',
         reward = 'Reward',
Line 126: Line 128:
         granted_skills_gem_output_format = '{level}{il}',
         granted_skills_gem_output_format = '{level}{il}',
         alternate_art = 'Alternate<br>Arts',
         alternate_art = 'Alternate<br>Arts',
       
 
         -- Skills
         -- Skills
         support_gem_letter = m_util.html.abbr('L', 'Support gem letter.'),
         support_gem_letter = m_util.html.abbr('L', 'Support gem letter.'),
Line 150: Line 152:
         quest_rewards_any_classes = 'any character',
         quest_rewards_any_classes = 'any character',
     },
     },
   
 
     prophecy_description = {
     prophecy_description = {
         objective = 'Objective',
         objective = 'Objective',
         reward = 'Reward',
         reward = 'Reward',
     },
     },
   
 
     item_disambiguation = {
     item_disambiguation = {
         original='the original variant',
         original='the original variant',
Line 236: Line 238:
         local fmt_values = {}
         local fmt_values = {}
         local sdata = data2.skill_levels[data['items._pageName']]
         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 257: Line 259:
             end
             end
         end
         end
       
 
         if #values == 0 then
         if #values == 0 then
             tr:wikitext(m_util.html.td.na())
             tr:wikitext(m_util.html.td.na())
Line 303: Line 305:
         for i=0,4 do
         for i=0,4 do
             local t = tonumber(data['atlas_maps.map_tier' .. i])
             local t = tonumber(data['atlas_maps.map_tier' .. i])
           
 
             if t == 0 then
             if t == 0 then
                 tr
                 tr
Line 357: Line 359:
         order = 1001,
         order = 1001,
         sort_type = 'text',
         sort_type = 'text',
    },
    {
        arg = 'rarity',
        header = i18n.item_table.rarity,
        fields = {'items.rarity'},
        display = h.tbl.display.factory.value{},
        order = 1002,
    },
    {
        arg = 'rarity_id',
        header = i18n.item_table.rarity_id,
        fields = {'items.rarity_id'},
        display = h.tbl.display.factory.value{},
        order = 1003,
     },
     },
     {
     {
Line 363: Line 379:
         fields = {'items.metadata_id'},
         fields = {'items.metadata_id'},
         display = h.tbl.display.factory.value{},
         display = h.tbl.display.factory.value{},
         order = 1002,
         order = 1004,
     },
     },
     {
     {
Line 441: Line 457:
         display = h.tbl.display.factory.range{field='maximum physical damage'},
         display = h.tbl.display.factory.range{field='maximum physical damage'},
         order = 7001,
         order = 7001,
       
 
     },]]--
     },]]--
     {
     {
Line 743: Line 759:
                     :wikitext(
                     :wikitext(
                         string.format(
                         string.format(
                             i18n.item_table.version_link,  
                             i18n.item_table.version_link,
                             data['items.release_version'],  
                             data['items.release_version'],
                             data['items.release_version']
                             data['items.release_version']
                         )
                         )
Line 760: Line 776:
                     :wikitext(
                     :wikitext(
                         string.format(
                         string.format(
                             i18n.item_table.version_link,  
                             i18n.item_table.version_link,
                             data['items.removal_version'],  
                             data['items.removal_version'],
                             data['items.removal_version']
                             data['items.removal_version']
                         )
                         )
Line 781: Line 797:
         display = function(tr, data)
         display = function(tr, data)
             local s = m_util.string.split(data['items.drop_leagues'], ',')
             local s = m_util.string.split(data['items.drop_leagues'], ',')
             for i, v in ipairs(s) do  
             for i, v in ipairs(s) do
                 s[i] = string.format(i18n.item_table.drop_leagues_link, v, v)
                 s[i] = string.format(i18n.item_table.drop_leagues_link, v, v)
             end
             end
Line 816: Line 832:
                     {'items', 'quest_rewards'},
                     {'items', 'quest_rewards'},
                     {
                     {
                         'quest_rewards.reward',  
                         'quest_rewards.reward',
                         'quest_rewards.classes',  
                         'quest_rewards.classes',
                         'quest_rewards.act',  
                         'quest_rewards.act',
                         'quest_rewards.quest'
                         'quest_rewards.quest'
                     },
                     },
Line 831: Line 847:
                 )
                 )
                 results2['quest_query'] = m_cargo.map_results_to_id{
                 results2['quest_query'] = m_cargo.map_results_to_id{
                     results=results2['quest_query'],  
                     results=results2['quest_query'],
                     field='quest_rewards.reward',
                     field='quest_rewards.reward',
                 }
                 }
             end
             end
           
 
             local results = results2['quest_query'][data['items._pageName']] or {}
             local results = results2['quest_query'][data['items._pageName']] or {}
             local tbl = {}
             local tbl = {}
             for _, v in ipairs(results) do  
             for _, v in ipairs(results) do
                 local classes = table.concat(m_util.string.split(v['quest_rewards.classes'] or '', '�'), ', ')  
                 local classes = table.concat(m_util.string.split(v['quest_rewards.classes'] or '', '�'), ', ')
                 if classes == '' or classes == nil then
                 if classes == '' or classes == nil then
                     classes = i18n.item_table.quest_rewards_any_classes
                     classes = i18n.item_table.quest_rewards_any_classes
                 end  
                 end
           
 
                 tbl[#tbl+1] = string.format(
                 tbl[#tbl+1] = string.format(
                     i18n.item_table.quest_rewards_row_format,
                     i18n.item_table.quest_rewards_row_format,
                     v['quest_rewards.act'],  
                     v['quest_rewards.act'],
                     v['quest_rewards.quest'],  
                     v['quest_rewards.quest'],
                     classes
                     classes
                 )
                 )
             end
             end
           
 
             value = table.concat(tbl, '<br>')
             value = table.concat(tbl, '<br>')
             if value == nil or value == '' then
             if value == nil or value == '' then
Line 874: Line 890:
                     {'items', 'vendor_rewards'},
                     {'items', 'vendor_rewards'},
                     {
                     {
                         'vendor_rewards.reward',  
                         'vendor_rewards.reward',
                         'vendor_rewards.classes',  
                         'vendor_rewards.classes',
                         'vendor_rewards.act',  
                         'vendor_rewards.act',
                         'vendor_rewards.npc',  
                         'vendor_rewards.npc',
                         'vendor_rewards.quest',
                         'vendor_rewards.quest',
                     },
                     },
Line 883: Line 899:
                         join='items._pageName=vendor_rewards.reward',
                         join='items._pageName=vendor_rewards.reward',
                         where=string.format(
                         where=string.format(
                             'items._pageID IN (%s) AND vendor_rewards.reward IS NOT NULL',  
                             'items._pageID IN (%s) AND vendor_rewards.reward IS NOT NULL',
                             table.concat(results2.pageIDs, ', ')
                             table.concat(results2.pageIDs, ', ')
                         ),
                         ),
Line 890: Line 906:
                 )
                 )
                 results2['vendor_query'] = m_cargo.map_results_to_id{
                 results2['vendor_query'] = m_cargo.map_results_to_id{
                     results=results2['vendor_query'],  
                     results=results2['vendor_query'],
                     field='vendor_rewards.reward',
                     field='vendor_rewards.reward',
                 }
                 }
             end
             end
             local results = results2['vendor_query'][data['items._pageName']] or {}
             local results = results2['vendor_query'][data['items._pageName']] or {}
           
 
             local tbl = {}
             local tbl = {}
             for _, v in ipairs(results) do  
             for _, v in ipairs(results) do
                 local classes = table.concat(m_util.string.split(v['vendor_rewards.classes'] or '', '�'), ', ')  
                 local classes = table.concat(m_util.string.split(v['vendor_rewards.classes'] or '', '�'), ', ')
                 if classes == '' or classes == nil then
                 if classes == '' or classes == nil then
                     classes = i18n.item_table.vendor_rewards_any_classes
                     classes = i18n.item_table.vendor_rewards_any_classes
                 end  
                 end
           
 
                 tbl[#tbl+1] = string.format(
                 tbl[#tbl+1] = string.format(
                     i18n.item_table.vendor_rewards_row_format,
                     i18n.item_table.vendor_rewards_row_format,
                     v['vendor_rewards.act'],  
                     v['vendor_rewards.act'],
                     v['vendor_rewards.quest'],  
                     v['vendor_rewards.quest'],
                     v['vendor_rewards.npc'],  
                     v['vendor_rewards.npc'],
                     classes
                     classes
                 )
                 )
             end
             end
           
 
             value = table.concat(tbl, '<br>')
             value = table.concat(tbl, '<br>')
             if value == nil or value == '' then
             if value == nil or value == '' then
Line 930: Line 946:
         fields = {'item_purchase_costs.name', 'item_purchase_costs.amount'},
         fields = {'item_purchase_costs.name', 'item_purchase_costs.amount'},
         display = function (tr, data)
         display = function (tr, data)
             -- Can purchase costs have multiple currencies and rows?  
             -- Can purchase costs have multiple currencies and rows?
             -- Just switch to the same method as in sell_price then.
             -- Just switch to the same method as in sell_price then.
             local tbl = {}
             local tbl = {}
             if data['item_purchase_costs.name'] ~= nil then  
             if data['item_purchase_costs.name'] ~= nil then
                 tbl[#tbl+1] = string.format(
                 tbl[#tbl+1] = string.format(
                         '%sx %s',  
                         '%sx %s',
                         data['item_purchase_costs.amount'],
                         data['item_purchase_costs.amount'],
                         f_item_link{data['item_purchase_costs.name']}
                         f_item_link{data['item_purchase_costs.name']}
Line 954: Line 970:
                     {'items', 'item_sell_prices'},
                     {'items', 'item_sell_prices'},
                     {
                     {
                         'item_sell_prices.name',  
                         'item_sell_prices.name',
                         'item_sell_prices.amount',  
                         'item_sell_prices.amount',
                         'item_sell_prices._pageID'
                         'item_sell_prices._pageID'
                     },
                     },
Line 961: Line 977:
                         join='items._pageID=item_sell_prices._pageID',
                         join='items._pageID=item_sell_prices._pageID',
                         where=string.format(
                         where=string.format(
                             'items._pageID IN (%s) AND item_sell_prices._pageID IS NOT NULL',  
                             'items._pageID IN (%s) AND item_sell_prices._pageID IS NOT NULL',
                             table.concat(results2.pageIDs, ', ')
                             table.concat(results2.pageIDs, ', ')
                         ),
                         ),
Line 968: Line 984:
                 )
                 )
                 results2['sell_price_query'] = m_cargo.map_results_to_id{
                 results2['sell_price_query'] = m_cargo.map_results_to_id{
                     results=results2['sell_price_query'],  
                     results=results2['sell_price_query'],
                     field='item_sell_prices._pageID',
                     field='item_sell_prices._pageID',
                 }
                 }
             end  
             end
             local results = results2['sell_price_query'][data['items._pageID']] or {}
             local results = results2['sell_price_query'][data['items._pageID']] or {}
           
 
             local tbl = {}
             local tbl = {}
             for _,v in ipairs(results) do
             for _,v in ipairs(results) do
                 tbl[#tbl+1] = string.format(
                 tbl[#tbl+1] = string.format(
                     '%sx %s',  
                     '%sx %s',
                     v['item_sell_prices.amount'],
                     v['item_sell_prices.amount'],
                     f_item_link{v['item_sell_prices.name']}
                     f_item_link{v['item_sell_prices.name']}
Line 990: Line 1,006:
         arg = {'boss', 'boss_name'},
         arg = {'boss', 'boss_name'},
         header = i18n.item_table.boss_name,
         header = i18n.item_table.boss_name,
         fields = {'maps.area_id'},  
         fields = {'maps.area_id'},
         display = function(tr, data, na, results2)
         display = function(tr, data, na, results2)
             if results2['boss_query'] == nil then  
             if results2['boss_query'] == nil then
                 results2['boss_query'] = m_cargo.query(
                 results2['boss_query'] = m_cargo.query(
                     {'items', 'maps', 'areas', 'monsters', 'main_pages'},
                     {'items', 'maps', 'areas', 'monsters', 'main_pages'},
                     {
                     {
                         'items._pageName',  
                         'items._pageName',
                         'maps.area_id',  
                         'maps.area_id',
                         'areas.id',  
                         'areas.id',
                         'areas.boss_monster_ids',
                         'areas.boss_monster_ids',
                         'monsters._pageName',
                         'monsters._pageName',
Line 1,006: Line 1,022:
                     {
                     {
                         join=[[
                         join=[[
                             items._pageID=maps._pageID,  
                             items._pageID=maps._pageID,
                             maps.area_id=areas.id,  
                             maps.area_id=areas.id,
                             areas.boss_monster_ids HOLDS monsters.metadata_id,  
                             areas.boss_monster_ids HOLDS monsters.metadata_id,
                             monsters.metadata_id=main_pages.id
                             monsters.metadata_id=main_pages.id
                         ]],
                         ]],
                         where=string.format([[
                         where=string.format([[
                             items._pageID IN (%s)  
                             items._pageID IN (%s)
                             AND maps.area_id IS NOT NULL  
                             AND maps.area_id IS NOT NULL
                             AND areas.boss_monster_ids HOLDS LIKE "%%"
                             AND areas.boss_monster_ids HOLDS LIKE "%%"
                         ]],  
                         ]],
                             table.concat(results2.pageIDs, ', ')
                             table.concat(results2.pageIDs, ', ')
                         ),
                         ),
                         orderBy='areas.boss_monster_ids',
                         orderBy='areas.boss_monster_ids',
                     }
                     }
                 )              
                 )
               
 
                 results2['boss_query'] = m_cargo.map_results_to_id{
                 results2['boss_query'] = m_cargo.map_results_to_id{
                     results=results2['boss_query'],  
                     results=results2['boss_query'],
                     field='items._pageName',
                     field='items._pageName',
                 }
                 }
             end  
             end
             local results = results2['boss_query'][data['items._pageName']] or {}
             local results = results2['boss_query'][data['items._pageName']] or {}
             local tbl = {}
             local tbl = {}
Line 1,042: Line 1,058:
         arg = {'boss', 'boss_number'},
         arg = {'boss', 'boss_number'},
         header = i18n.item_table.boss_number,
         header = i18n.item_table.boss_number,
         fields = {'maps.area_id'},  
         fields = {'maps.area_id'},
         display = function(tr, data, na, results2)
         display = function(tr, data, na, results2)
             if results2['boss_query'] == nil then  
             if results2['boss_query'] == nil then
                 results2['boss_query'] = m_cargo.query(
                 results2['boss_query'] = m_cargo.query(
                     {'items', 'maps', 'areas', 'monsters', 'main_pages'},
                     {'items', 'maps', 'areas', 'monsters', 'main_pages'},
                     {
                     {
                         'items._pageName',  
                         'items._pageName',
                         'maps.area_id',  
                         'maps.area_id',
                         'areas.id',  
                         'areas.id',
                         'areas.boss_monster_ids',
                         'areas.boss_monster_ids',
                         'monsters._pageName',
                         'monsters._pageName',
Line 1,058: Line 1,074:
                     {
                     {
                         join=[[
                         join=[[
                             items._pageID=maps._pageID,  
                             items._pageID=maps._pageID,
                             maps.area_id=areas.id,  
                             maps.area_id=areas.id,
                             areas.boss_monster_ids HOLDS monsters.metadata_id,  
                             areas.boss_monster_ids HOLDS monsters.metadata_id,
                             monsters.metadata_id=main_pages.id
                             monsters.metadata_id=main_pages.id
                         ]],
                         ]],
                         where=string.format([[
                         where=string.format([[
                             items._pageID IN (%s)  
                             items._pageID IN (%s)
                             AND maps.area_id IS NOT NULL  
                             AND maps.area_id IS NOT NULL
                             AND areas.boss_monster_ids HOLDS LIKE "%%"
                             AND areas.boss_monster_ids HOLDS LIKE "%%"
                         ]],  
                         ]],
                             table.concat(results2.pageIDs, ', ')
                             table.concat(results2.pageIDs, ', ')
                         ),
                         ),
Line 1,073: Line 1,089:
                     }
                     }
                 )
                 )
               
 
                 results2['boss_query'] = m_cargo.map_results_to_id{
                 results2['boss_query'] = m_cargo.map_results_to_id{
                     results=results2['boss_query'],  
                     results=results2['boss_query'],
                     field='items._pageName',
                     field='items._pageName',
                 }
                 }
             end  
             end
             local results = results2['boss_query'][data['items._pageName']] or {}
             local results = results2['boss_query'][data['items._pageName']] or {}
             local tbl = {}
             local tbl = {}
Line 1,091: Line 1,107:
         arg = {'sextant'},
         arg = {'sextant'},
         header = i18n.item_table.sextant,
         header = i18n.item_table.sextant,
         fields = {'items.name', 'atlas_maps.x', 'atlas_maps.y', 'maps.series'},  
         fields = {'items.name', 'atlas_maps.x', 'atlas_maps.y', 'maps.series'},
         display = function(tr, data, na, results2)
         display = function(tr, data, na, results2)
             if results2['sextant_query'] == nil then  
             if results2['sextant_query'] == nil then
                 results2['sextant_query'] = m_cargo.query(
                 results2['sextant_query'] = m_cargo.query(
                     {'items', 'maps', 'atlas_maps'},
                     {'items', 'maps', 'atlas_maps'},
                     {
                     {
                         'items._pageName',  
                         'items._pageName',
                         'items.name',  
                         'items.name',
                         'maps.series',  
                         'maps.series',
                         'atlas_maps.x',  
                         'atlas_maps.x',
                         'atlas_maps.y',  
                         'atlas_maps.y',
                         'atlas_maps.connections'
                         'atlas_maps.connections'
                     },
                     },
Line 1,110: Line 1,126:
                     }
                     }
                 )
                 )
             end  
             end
             local results = results2['sextant_query'] or {}
             local results = results2['sextant_query'] or {}
           
 
             local sextant_radius = 55 -- Should be in Module:Game?
             local sextant_radius = 55 -- Should be in Module:Game?
             local x_center = data['atlas_maps.x']
             local x_center = data['atlas_maps.x']
             local y_center = data['atlas_maps.y']
             local y_center = data['atlas_maps.y']
           
 
             if not (x_center and y_center) then  
             if not (x_center and y_center) then
                 return  
                 return
             end  
             end
           
 
             local tbl = {}
             local tbl = {}
             for _, v in ipairs(results) do  
             for _, v in ipairs(results) do
                 local x = v['atlas_maps.x']
                 local x = v['atlas_maps.x']
                 local y = v['atlas_maps.y']
                 local y = v['atlas_maps.y']
                 local r = ((x-x_center)^2 + (y-y_center)^2)^0.5
                 local r = ((x-x_center)^2 + (y-y_center)^2)^0.5
                 if (sextant_radius >= r) and (data['items._pageName'] ~= v['items._pageName']) then --
                 if (sextant_radius >= r) and (data['items._pageName'] ~= v['items._pageName']) then --
                     tbl[#tbl+1] = f_item_link({page=v['items._pageName']})  
                     tbl[#tbl+1] = f_item_link({page=v['items._pageName']})
                 end
                 end
             end
             end
Line 1,138: Line 1,154:
         arg = {'legacy'},
         arg = {'legacy'},
         header = i18n.item_table.legacy,
         header = i18n.item_table.legacy,
         fields = {'items.name'},  
         fields = {'items.name'},
         display = function(tr, data, na, results2)
         display = function(tr, data, na, results2)
             if results2['legacy_query'] == nil then  
             if results2['legacy_query'] == nil then
                 results2['legacy_query'] = m_cargo.query(
                 results2['legacy_query'] = m_cargo.query(
                     {'items', 'legacy_variants'},
                     {'items', 'legacy_variants'},
                     {
                     {
                         'items._pageID',  
                         'items._pageID',
                         'items._pageName',  
                         'items._pageName',
                         'items.frame_type',  
                         'items.frame_type',
                         'legacy_variants.removal_version',  
                         'legacy_variants.removal_version',
                         'legacy_variants.implicit_stat_text',  
                         'legacy_variants.implicit_stat_text',
                         'legacy_variants.explicit_stat_text',  
                         'legacy_variants.explicit_stat_text',
                         'legacy_variants.stat_text',  
                         'legacy_variants.stat_text',
                         'legacy_variants.base_item',  
                         'legacy_variants.base_item',
                         'legacy_variants.required_level'
                         'legacy_variants.required_level'
                     },
                     },
Line 1,158: Line 1,174:
                         where='legacy_variants.removal_version IS NOT NULL',
                         where='legacy_variants.removal_version IS NOT NULL',
                         where=string.format(
                         where=string.format(
                             'items._pageID IN (%s) AND legacy_variants.removal_version IS NOT NULL',  
                             'items._pageID IN (%s) AND legacy_variants.removal_version IS NOT NULL',
                             table.concat(results2.pageIDs, ', ')
                             table.concat(results2.pageIDs, ', ')
                         ),
                         ),
Line 1,164: Line 1,180:
                     }
                     }
                 )
                 )
               
 
                 results2['legacy_query'] = m_cargo.map_results_to_id{
                 results2['legacy_query'] = m_cargo.map_results_to_id{
                     results=results2['legacy_query'],  
                     results=results2['legacy_query'],
                     field='items._pageName',
                     field='items._pageName',
                 }
                 }
             end  
             end
             local results = results2['legacy_query'][data['items._pageName']] or {}
             local results = results2['legacy_query'][data['items._pageName']] or {}
           
 
             local tbl = mw.html.create('table')
             local tbl = mw.html.create('table')
                 :attr('width', '100%')
                 :attr('width', '100%')
Line 1,177: Line 1,193:
                 local cell = {}
                 local cell = {}
                 local l = {
                 local l = {
                     'legacy_variants.base_item',  
                     'legacy_variants.base_item',
                     'legacy_variants.stat_text'
                     'legacy_variants.stat_text'
                 }
                 }
               
 
                 -- Clean up data:  
                 -- Clean up data:
                 for _, k in ipairs(l) do
                 for _, k in ipairs(l) do
                     if v[k] ~= nil then  
                     if v[k] ~= nil then
                         local s = m_util.string.split(v[k], '*')
                         local s = m_util.string.split(v[k], '*')
                         local s_flt = {}
                         local s_flt = {}
                         for _, sss in ipairs(s) do  
                         for _, sss in ipairs(s) do
                             if sss ~= nil and sss ~= '' then  
                             if sss ~= nil and sss ~= '' then
                                 s_flt[#s_flt+1] = string.gsub(sss, '\n', '')
                                 s_flt[#s_flt+1] = string.gsub(sss, '\n', '')
                             end
                             end
                         end  
                         end
                       
 
                         cell[#cell+1] = table.concat(s_flt, '<br>')
                         cell[#cell+1] = table.concat(s_flt, '<br>')
                     end  
                     end
                 end
                 end
               
 
                 local sep = string.format(
                 local sep = string.format(
                     '<span class="item-stat-separator -%s"></span>',  
                     '<span class="item-stat-separator -%s"></span>',
                     v['items.frame_type']
                     v['items.frame_type']
                 )
                 )
Line 1,224: Line 1,240:
         arg = {'granted_skills'},
         arg = {'granted_skills'},
         header = i18n.item_table.granted_skills,
         header = i18n.item_table.granted_skills,
         fields = {'items.name'},  
         fields = {'items.name'},
         display = function(tr, data, na, results2)
         display = function(tr, data, na, results2)
             if results2['granted_skills_query'] == nil then  
             if results2['granted_skills_query'] == nil then
                 results2['granted_skills_query'] = m_cargo.query(
                 results2['granted_skills_query'] = m_cargo.query(
                     {'items', 'item_mods', 'mods', 'skill', 'items=items2'},
                     {'items', 'item_mods', 'mods', 'skill', 'items=items2'},
                     {
                     {
                         'items._pageName',  
                         'items._pageName',
                         'items.name',  
                         'items.name',
                         'item_mods.id',  
                         'item_mods.id',
                         'mods._pageName',  
                         'mods._pageName',
                         'mods.id',  
                         'mods.id',
                         'mods.granted_skill',
                         'mods.granted_skill',
                         'mods.stat_text_raw',
                         'mods.stat_text_raw',
Line 1,252: Line 1,268:
                         join='items._pageID=item_mods._pageID, item_mods.id=mods.id, mods.granted_skill=skill.skill_id, skill._pageID=items2._pageID',
                         join='items._pageID=item_mods._pageID, item_mods.id=mods.id, mods.granted_skill=skill.skill_id, skill._pageID=items2._pageID',
                         where=string.format(
                         where=string.format(
                             'items._pageID IN (%s) AND mods.granted_skill IS NOT NULL',  
                             'items._pageID IN (%s) AND mods.granted_skill IS NOT NULL',
                             table.concat(results2.pageIDs, ', ')
                             table.concat(results2.pageIDs, ', ')
                         ),
                         ),
                     }
                     }
                 )
                 )
               
 
                 results2['granted_skills_query'] = m_cargo.map_results_to_id{
                 results2['granted_skills_query'] = m_cargo.map_results_to_id{
                     results=results2['granted_skills_query'],  
                     results=results2['granted_skills_query'],
                     field='items._pageName',
                     field='items._pageName',
                 }
                 }
             end  
             end
             local results = results2['granted_skills_query'][data['items._pageName']] or {}
             local results = results2['granted_skills_query'][data['items._pageName']] or {}
           
 
             local tbl = {}
             local tbl = {}
             for _, v in ipairs(results) do
             for _, v in ipairs(results) do
           
 
                 -- Check if a level for the skill is specified in the  
                 -- Check if a level for the skill is specified in the
                 -- mod stat text.
                 -- mod stat text.
                 -- Stat ids have unreliable naming convention so using
                 -- Stat ids have unreliable naming convention so using
Line 1,275: Line 1,291:
                 local stat_text = v['mods.stat_text_raw'] or ''
                 local stat_text = v['mods.stat_text_raw'] or ''
                 local level_number = string.match(
                 local level_number = string.match(
                     stat_text:lower(),  
                     stat_text:lower(),
                     m_util.string.format(
                     m_util.string.format(
                         i18n.item_table.granted_skills_level_pattern,
                         i18n.item_table.granted_skills_level_pattern,
Line 1,283: Line 1,299:
                     )
                     )
                 )
                 )
               
 
                 -- If a level number was specified in the stat text  
                 -- If a level number was specified in the stat text
                 -- then add it to the cell:  
                 -- then add it to the cell:
                 if level_number then
                 if level_number then
                     level = m_util.string.format(
                     level = m_util.string.format(
Line 1,295: Line 1,311:
                     )
                     )
                 end
                 end
               
 
                 -- Use different formats depending on if it's a gem or  
                 -- Use different formats depending on if it's a gem or
                 -- not:
                 -- not:
                 if v['items2.class'] == nil  then
                 if v['items2.class'] == nil  then
Line 1,305: Line 1,321:
                             sl = f_skill_link{
                             sl = f_skill_link{
                                 skip_query=true,
                                 skip_query=true,
                                 page = v['skill.active_skill_name']  
                                 page = v['skill.active_skill_name']
                                     or v['skill._pageName']  
                                     or v['skill._pageName']
                                     or v['mods._pageName']  
                                     or v['mods._pageName']
                                     or '',
                                     or '',
                                 name = v['skill.active_skill_name']  
                                 name = v['skill.active_skill_name']
                                     or v['skill.stat_text']  
                                     or v['skill.stat_text']
                                     or v['mods.granted_skill'],
                                     or v['mods.granted_skill'],
                                 icon = v['skill.skill_icon'],
                                 icon = v['skill.skill_icon'],
Line 1,319: Line 1,335:
                     local il_args = {
                     local il_args = {
                         skip_query=true,
                         skip_query=true,
                         page=v['items2._pageName'],  
                         page=v['items2._pageName'],
                         name=v['items2.name'],  
                         name=v['items2.name'],
                         inventory_icon=v['items2.inventory_icon'],  
                         inventory_icon=v['items2.inventory_icon'],
                         width=v['items2.size_x'],
                         width=v['items2.size_x'],
                         height=v['items2.size_y'],
                         height=v['items2.size_y'],
                     }
                     }
                   
 
                     -- TODO: add in tpl_args.
                     -- TODO: add in tpl_args.
                     if no_html == nil then
                     if no_html == nil then
Line 1,350: Line 1,366:
         display = function (tr, data)
         display = function (tr, data)
             local alt_art = m_util.string.split(
             local alt_art = m_util.string.split(
                 data['items.alternate_art_inventory_icons'],  
                 data['items.alternate_art_inventory_icons'],
                 ','
                 ','
             )
             )
           
 
             -- TODO: Use il instead to handle size?
             -- TODO: Use il instead to handle size?
             -- local size = 39
             -- local size = 39
Line 1,359: Line 1,375:
             for i,v in ipairs(alt_art) do
             for i,v in ipairs(alt_art) do
                 out[#out+1] = string.format(
                 out[#out+1] = string.format(
                     '[[%s|link=|%s]]',  
                     '[[%s|link=|%s]]',
                     v,
                     v,
                     v
                     v
                 )
                 )
             end  
             end
           
 
             tr
             tr
                 :tag('td')
                 :tag('td')
Line 1,516: Line 1,532:
                 appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
                 appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
             end
             end
           
 
             h.tbl.display.factory.value{options = {
             h.tbl.display.factory.value{options = {
                 [1] = {
                 [1] = {
Line 1,634: Line 1,650:
local p = {}
local p = {}


--  
--
-- Template:Item table
-- Template:Item table
--  
--


function p.item_table(frame)
function p.item_table(frame)
     --[[
     --[[
     Creates a generic table for items.
     Creates a generic table for items.
   
 
     Examples
     Examples
     --------
     --------
Line 1,650: Line 1,666:
         vendor=1,
         vendor=1,
     }
     }
   
 
     ]]
     ]]
   
 
     local t = os.clock()
     local t = os.clock()
     -- args
     -- args
Line 1,659: Line 1,675:
         })
         })
     frame = m_util.misc.get_frame(frame)
     frame = m_util.misc.get_frame(frame)
   
 
     tpl_args.q_where = m_cargo.replace_holds{string=tpl_args.q_where}
     tpl_args.q_where = m_cargo.replace_holds{string=tpl_args.q_where}


Line 1,672: Line 1,688:
         },
         },
     }
     }
   
 
     if tpl_args.mode == nil then
     if tpl_args.mode == nil then
         tpl_args.mode = 'item'
         tpl_args.mode = 'item'
     end
     end
   
 
     if modes[tpl_args.mode] == nil then
     if modes[tpl_args.mode] == nil then
         error(i18n.errors.invalid_item_table_mode)
         error(i18n.errors.invalid_item_table_mode)
     end
     end
   
 
     local results2 = {
     local results2 = {
         stats = {},
         stats = {},
Line 1,686: Line 1,702:
         pageIDs = {},
         pageIDs = {},
     }
     }
   
 
     local row_infos = {}
     local row_infos = {}
     for _, row_info in ipairs(modes[tpl_args.mode].data) do
     for _, row_info in ipairs(modes[tpl_args.mode].data) do
Line 1,694: Line 1,710:
         elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then
         elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then
             enabled = true
             enabled = true
         elseif type(row_info.arg) == 'table' then  
         elseif type(row_info.arg) == 'table' then
             for _, argument in ipairs(row_info.arg) do
             for _, argument in ipairs(row_info.arg) do
                 if m_util.cast.boolean(tpl_args[argument]) then
                 if m_util.cast.boolean(tpl_args[argument]) then
Line 1,702: Line 1,718:
             end
             end
         end
         end
       
 
         if enabled then
         if enabled then
             row_info.options = row_info.options or {}
             row_info.options = row_info.options or {}
Line 1,708: Line 1,724:
         end
         end
     end
     end
 
 
     -- Parse stat arguments
     -- Parse stat arguments
     local stat_columns = {}
     local stat_columns = {}
Line 1,715: Line 1,731:
     repeat
     repeat
         i = i + 1
         i = i + 1
       
 
         local prefix = string.format('stat_column%s_', i)
         local prefix = string.format('stat_column%s_', i)
         local col_info = {
         local col_info = {
Line 1,725: Line 1,741:
             options = {},
             options = {},
         }
         }
       
 
         local j = 0
         local j = 0
         repeat
         repeat
             j = j +1
             j = j +1
       
 
             local stat_info = {
             local stat_info = {
                 id = tpl_args[string.format('%sstat%s_id', prefix, j)],
                 id = tpl_args[string.format('%sstat%s_id', prefix, j)],
             }
             }
           
 
             if stat_info.id then
             if stat_info.id then
                 col_info.stats[#col_info.stats+1] = stat_info
                 col_info.stats[#col_info.stats+1] = stat_info
                 query_stats[stat_info.id] = {}  
                 query_stats[stat_info.id] = {}
             else
             else
                 -- Stop iteration entirely if this was the first index but no stat was supplied. We assume that we stop in this case.
                 -- Stop iteration entirely if this was the first index but no stat was supplied. We assume that we stop in this case.
Line 1,746: Line 1,762:
             end
             end
         until j == nil
         until j == nil
       
 
         -- Don't add this column if no stats were provided.  
         -- Don't add this column if no stats were provided.
         if #col_info.stats > 0 then
         if #col_info.stats > 0 then
             stat_columns[#stat_columns+1] = col_info
             stat_columns[#stat_columns+1] = col_info
         end
         end
     until i == nil
     until i == nil
 
 
     for _, col_info in ipairs(stat_columns) do
     for _, col_info in ipairs(stat_columns) do
         local row_info = {
         local row_info = {
Line 1,772: Line 1,788:
                         end
                         end
                     end
                     end
                   
 
                     if num_stats ~= #stat_texts then
                     if num_stats ~= #stat_texts then
                         tr:wikitext(m_util.html.td.na())
                         tr:wikitext(m_util.html.td.na())
Line 1,782: Line 1,798:
                             text = table.concat(stat_texts, ', ')
                             text = table.concat(stat_texts, ', ')
                         end
                         end
                   
 
                         tr:tag('td')
                         tr:tag('td')
                             :attr('data-sort-value', vmax)
                             :attr('data-sort-value', vmax)
Line 1,802: Line 1,818:
                         end
                         end
                     end
                     end
                   
 
                     if col_info.format == nil then
                     if col_info.format == nil then
                         col_info.format = '%s'
                         col_info.format = '%s'
                     end
                     end
                   
 
                     tr:tag('td')
                     tr:tag('td')
                         :attr('data-sort-value', total_stat.max)
                         :attr('data-sort-value', total_stat.max)
Line 1,819: Line 1,835:
         table.insert(row_infos, row_info)
         table.insert(row_infos, row_info)
     end
     end
       
 
     -- sort the rows
     -- sort the rows
     table.sort(row_infos, function (a, b)
     table.sort(row_infos, function (a, b)
         return (a.order or 0) < (b.order or 0)
         return (a.order or 0) < (b.order or 0)
     end)
     end)
   
 
     -- Parse query arguments
     -- Parse query arguments
     local tables_assoc = {items=true}
     local tables_assoc = {items=true}
Line 1,836: Line 1,852:
         'items.size_y',
         'items.size_y',
     }
     }
   
 
     --
     --
     local prepend = {
     local prepend = {
Line 1,842: Line 1,858:
         q_tables=true,
         q_tables=true,
     }
     }
   
 
     local query = {}
     local query = {}
     for key, value in pairs(tpl_args) do  
     for key, value in pairs(tpl_args) do
         if string.sub(key, 0, 2) == 'q_' then
         if string.sub(key, 0, 2) == 'q_' then
             if prepend[key] then
             if prepend[key] then
                 value = ',' .. value
                 value = ',' .. value
             end
             end
           
 
             query[string.sub(key, 3)] = value
             query[string.sub(key, 3)] = value
         end
         end
     end
     end
   
 
     local skill_levels = {}
     local skill_levels = {}
     for _, rowinfo in ipairs(row_infos) do
     for _, rowinfo in ipairs(row_infos) do
Line 1,873: Line 1,889:
         fields[#fields+1] = 'skill.max_level'
         fields[#fields+1] = 'skill.max_level'
         tables_assoc.skill = true
         tables_assoc.skill = true
   
 
     end
     end
   
 
     -- Reformat the tables and fields so they can be retrieved correctly:  
     -- Reformat the tables and fields so they can be retrieved correctly:
     local tables = {}
     local tables = {}
     for table_name,_ in pairs(tables_assoc) do
     for table_name,_ in pairs(tables_assoc) do
Line 1,883: Line 1,899:
     local tbls = table.concat(tables,',') .. (query.tables or '')
     local tbls = table.concat(tables,',') .. (query.tables or '')
     query.tables = m_util.string.split(tbls, ',')
     query.tables = m_util.string.split(tbls, ',')
   
 
     for index, field in ipairs(fields) do
     for index, field in ipairs(fields) do
         fields[index] = string.format('%s=%s', field, field)
         fields[index] = string.format('%s=%s', field, field)
     end
     end
     query.fields = fields
     query.fields = fields
   
 
     -- Take care of the minimum required joins, joins from templates  
     -- Take care of the minimum required joins, joins from templates
     -- must still be userdefined:
     -- must still be userdefined:
     local joins = {}
     local joins = {}
     for index, table_name in ipairs(tables) do
     for index, table_name in ipairs(tables) do
         if table_name ~= 'items' then
         if table_name ~= 'items' then
             joins[#joins+1] = string.format('items._pageID=%s._pageID', table_name)  
             joins[#joins+1] = string.format('items._pageID=%s._pageID', table_name)
         end
         end
     end
     end
     if #joins > 0 and query.join then
     if #joins > 0 and query.join then
         query.join = table.concat(joins, ',') .. ',' .. query.join  
         query.join = table.concat(joins, ',') .. ',' .. query.join
     elseif #joins > 0 and not query.join then
     elseif #joins > 0 and not query.join then
         query.join = table.concat(joins, ',')
         query.join = table.concat(joins, ',')
Line 1,904: Line 1,920:
         -- leave query.join as is
         -- leave query.join as is
     end
     end
   
 
     -- Needed to eliminate duplicates supplied via table joins:
     -- Needed to eliminate duplicates supplied via table joins:
     query.groupBy = 'items._pageID' .. (query.groupBy or '')
     query.groupBy = 'items._pageID' .. (query.groupBy or '')
   
 
     -- Query results:
     -- Query results:
     local results = m_cargo.query(query.tables, query.fields, query)
     local results = m_cargo.query(query.tables, query.fields, query)
Line 1,914: Line 1,930:
         return tpl_args.default
         return tpl_args.default
     end
     end
   
 
     if #results > 0 then
     if #results > 0 then
         -- Create a list of found pageIDs for column specific queries:
         -- Create a list of found pageIDs for column specific queries:
         for _,v in ipairs(results) do  
         for _,v in ipairs(results) do
             results2.pageIDs[#results2.pageIDs+1] = v['items._pageID']
             results2.pageIDs[#results2.pageIDs+1] = v['items._pageID']
         end
         end
       
 
         -- fetch skill level information
         -- fetch skill level information
         if #skill_levels > 0 then
         if #skill_levels > 0 then
Line 1,946: Line 1,962:
             end
             end
         end
         end
   
 
         if #stat_columns > 0 then
         if #stat_columns > 0 then
             local pages = {}
             local pages = {}
Line 1,952: Line 1,968:
                 pages[#pages+1] = string.format('item_stats._pageID="%s"', row['items._pageID'])
                 pages[#pages+1] = string.format('item_stats._pageID="%s"', row['items._pageID'])
             end
             end
           
 
             local query_stat_ids = {}
             local query_stat_ids = {}
             for stat_id, _ in pairs(query_stats) do
             for stat_id, _ in pairs(query_stats) do
                 query_stat_ids[#query_stat_ids+1] = string.format('item_stats.id="%s"', stat_id)
                 query_stat_ids[#query_stat_ids+1] = string.format('item_stats.id="%s"', stat_id)
             end
             end
       
 
             if tpl_args.q_where then
             if tpl_args.q_where then
                 tpl_args.q_where = string.format(' AND (%s)', tpl_args.q_where)
                 tpl_args.q_where = string.format(' AND (%s)', tpl_args.q_where)
Line 1,963: Line 1,979:
                 tpl_args.q_where = ''
                 tpl_args.q_where = ''
             end
             end
           
 
             local temp = m_cargo.query(
             local temp = m_cargo.query(
                 {'items', 'item_stats'},
                 {'items', 'item_stats'},
Line 1,970: Line 1,986:
                     where=string.format('item_stats.is_implicit IS NULL AND (%s) AND (%s)', table.concat(query_stat_ids, ' OR '), table.concat(pages, ' OR ')),
                     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',
                     join='items._pageID=item_stats._pageID',
                     -- Cargo workaround: avoid duplicates using groupBy  
                     -- Cargo workaround: avoid duplicates using groupBy
                     groupBy='items._pageID, item_stats.id',
                     groupBy='items._pageID, item_stats.id',
                 }
                 }
Line 1,981: Line 1,997:
                     avg = tonumber(row['item_stats.avg']),
                     avg = tonumber(row['item_stats.avg']),
                 }
                 }
               
 
                 if results2.stats[row['item_stats._pageName']] == nil then
                 if results2.stats[row['item_stats._pageName']] == nil then
                     results2.stats[row['item_stats._pageName']] = {[row['item_stats.id']] = stat}
                     results2.stats[row['item_stats._pageName']] = {[row['item_stats.id']] = stat}
Line 1,990: Line 2,006:
         end
         end
     end
     end
   
 
   
 
     --
     --
     -- Display the table
     -- Display the table
     --
     --
   
 
     local tbl = mw.html.create('table')
     local tbl = mw.html.create('table')
     tbl:attr('class', 'wikitable sortable item-table')
     tbl:attr('class', 'wikitable sortable item-table')
   
 
     -- Headers:
     -- Headers:
     local tr = tbl:tag('tr')
     local tr = tbl:tag('tr')
Line 2,007: Line 2,023:
     for _, row_info in ipairs(row_infos) do
     for _, row_info in ipairs(row_infos) do
         local th = tr:tag('th')
         local th = tr:tag('th')
       
 
         if row_info.colspan then
         if row_info.colspan then
             th:attr('colspan', row_info.colspan)
             th:attr('colspan', row_info.colspan)
         end
         end
       
 
         th  
         th
             :attr('data-sort-type', row_info.sort_type or 'number')
             :attr('data-sort-type', row_info.sort_type or 'number')
             :wikitext(row_info.header)
             :wikitext(row_info.header)
     end
     end
   
 
     -- Rows:
     -- Rows:
     for _, row in ipairs(results) do
     for _, row in ipairs(results) do
         tr = tbl:tag('tr')
         tr = tbl:tag('tr')
       
 
         local il_args = {
         local il_args = {
             skip_query=true,
             skip_query=true,
             page=row['items._pageName'],  
             page=row['items._pageName'],
             name=row['items.name'],  
             name=row['items.name'],
             inventory_icon=row['items.inventory_icon'],  
             inventory_icon=row['items.inventory_icon'],
             width=row['items.size_x'],
             width=row['items.size_x'],
             height=row['items.size_y'],
             height=row['items.size_y'],
         }
         }
       
 
         if tpl_args.no_html == nil then
         if tpl_args.no_html == nil then
             il_args.html = row['items.html']
             il_args.html = row['items.html']
         end
         end
       
 
         if tpl_args.large then
         if tpl_args.large then
             il_args.large = tpl_args.large
             il_args.large = tpl_args.large
         end
         end
       
 
         tr
         tr
             :tag('td')
             :tag('td')
                 :wikitext(f_item_link(il_args))
                 :wikitext(f_item_link(il_args))
                 :done()
                 :done()
               
 
         for _, rowinfo in ipairs(row_infos) do
         for _, rowinfo in ipairs(row_infos) do
             -- this has been cast from a function in an earlier step
             -- this has been cast from a function in an earlier step
Line 2,070: Line 2,086:
         cats[#cats+1] = i18n.categories.query_limit
         cats[#cats+1] = i18n.categories.query_limit
     end
     end
   
 
     if #results == 0 then
     if #results == 0 then
         cats[#cats+1] = i18n.categories.no_results
         cats[#cats+1] = i18n.categories.no_results
     end
     end
   
 
     mw.logObject({os.clock() - t, query})
     mw.logObject({os.clock() - t, query})
   
 
     return tostring(tbl) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
     return tostring(tbl) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
end
end
Line 2,087: Line 2,103:
function p.map_item_drops(frame)
function p.map_item_drops(frame)
     --[[
     --[[
     Gets the area id from the map item and activates  
     Gets the area id from the map item and activates
     Template:Area_item_drops.
     Template:Area_item_drops.
   
 
     Examples:
     Examples:
     = p.map_item_drops{page='Underground River Map (War for the Atlas)'}
     = p.map_item_drops{page='Underground River Map (War for the Atlas)'}
     ]]
     ]]
   
 
     -- Get args
     -- Get args
     local tpl_args = getArgs(frame, {
     local tpl_args = getArgs(frame, {
Line 2,099: Line 2,115:
     })
     })
     frame = m_util.misc.get_frame(frame)
     frame = m_util.misc.get_frame(frame)
   
 
     tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
     tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
   
 
     local results = m_cargo.query(
     local results = m_cargo.query(
         {'maps'},
         {'maps'},
Line 2,115: Line 2,131:
         id = results[1]['maps.area_id']
         id = results[1]['maps.area_id']
     end
     end
     return frame:expandTemplate{ title = 'Area item drops', args = {area_id=id} }  
     return frame:expandTemplate{ title = 'Area item drops', args = {area_id=id} }
end
end


Line 2,128: Line 2,144:
     })
     })
     frame = m_util.misc.get_frame(frame)
     frame = m_util.misc.get_frame(frame)
   
 
     tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
     tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
   
 
     local results = m_cargo.query(
     local results = m_cargo.query(
         {'prophecies'},
         {'prophecies'},
Line 2,140: Line 2,156:
         }
         }
     )
     )
   
 
     results = results[1]
     results = results[1]
   
 
     local out = {}
     local out = {}
   
 
     if results['prophecies.objective'] then
     if results['prophecies.objective'] then
         out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.objective)
         out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.objective)
         out[#out+1] = results['prophecies.objective']
         out[#out+1] = results['prophecies.objective']
     end
     end
   
 
     if results['prophecies.reward'] then
     if results['prophecies.reward'] then
         out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.reward)
         out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.reward)
         out[#out+1] = results['prophecies.reward']
         out[#out+1] = results['prophecies.reward']
     end
     end
   
 
     return table.concat(out, '\n')
     return table.concat(out, '\n')
end
end
Line 2,163: Line 2,179:
function h.find_aliases(tpl_args)
function h.find_aliases(tpl_args)
   --[[
   --[[
   This function queries items for an item name, then checks if it has  
   This function queries items for an item name, then checks if it has
   had any name changes then queries for that name as well.
   had any name changes then queries for that name as well.
   ]]
   ]]
   
 
     -- Get initial name:
     -- Get initial name:
     tpl_args.name_list = {
     tpl_args.name_list = {
         tpl_args.name or m_util.string.split(
         tpl_args.name or m_util.string.split(
             tostring(mw.title.getCurrentTitle()),  
             tostring(mw.title.getCurrentTitle()),
             ' %('
             ' %('
         )
         )
     }
     }
   
 
     -- Query for items with similar name, repeat until no new names are  
     -- Query for items with similar name, repeat until no new names are
     -- found.
     -- found.
     local n
     local n
Line 2,182: Line 2,198:
     repeat
     repeat
         local n_old = #tpl_args.name_list
         local n_old = #tpl_args.name_list
       
 
         -- Multiple HOLDS doesn't work. Using __FULL and REGEXP instead.
         -- Multiple HOLDS doesn't work. Using __FULL and REGEXP instead.
         local where_tbl = {}
         local where_tbl = {}
Line 2,195: Line 2,211:
         end
         end
         local where_str = table.concat(where_tbl, ' OR ')
         local where_str = table.concat(where_tbl, ' OR ')
       
 
         results = m_cargo.query(
         results = m_cargo.query(
             {'items', 'maps'},
             {'items', 'maps'},
Line 2,213: Line 2,229:
             }
             }
         )
         )
       
 
         -- Filter duplicates:
         -- Filter duplicates:
         for i,v in ipairs(results) do
         for i,v in ipairs(results) do
Line 2,220: Line 2,236:
                 r = {r}
                 r = {r}
             end
             end
           
 
             for j,m in ipairs(r) do
             for j,m in ipairs(r) do
                 if hash[m] == nil then
                 if hash[m] == nil then
Line 2,229: Line 2,245:
         end
         end
     until #tpl_args.name_list == n_old
     until #tpl_args.name_list == n_old
   
 
     return results
     return results
end
end
Line 2,236: Line 2,252:
     --[[
     --[[
     This function finds that items with a name or has had that name.
     This function finds that items with a name or has had that name.
   
 
     To do
     To do
     -----
     -----
     Should text imply which is the original map, even if it isn't (Original)?
     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 properly sort drop disabled items, with removal version?
     How to deal with names that have been used multiple times? Terrace Map
     How to deal with names that have been used multiple times? Terrace Map
   
 
     Examples
     Examples
     --------
     --------
Line 2,251: Line 2,267:
     = p.item_disambiguation{name='Harbinger Map (High Tier)'}
     = p.item_disambiguation{name='Harbinger Map (High Tier)'}
     ]]
     ]]
   
 
     -- Get template arguments.
     -- Get template arguments.
     local tpl_args = getArgs(frame, {
     local tpl_args = getArgs(frame, {
Line 2,257: Line 2,273:
     })
     })
     frame = m_util.misc.get_frame(frame)
     frame = m_util.misc.get_frame(frame)
   
 
     local current_title = tostring(mw.title.getCurrentTitle())
     local current_title = tostring(mw.title.getCurrentTitle())
     -- Get the page name.
     -- Get the page name.
     tpl_args.name = tpl_args.name or m_util.string.split(
     tpl_args.name = tpl_args.name or m_util.string.split(
         current_title,  
         current_title,
         ' %('
         ' %('
     )[1]
     )[1]
   
 
     -- Query for items with similar name.
     -- Query for items with similar name.
     local results = h.find_aliases(tpl_args)
     local results = h.find_aliases(tpl_args)
   
 
     -- Format the results:
     -- Format the results:
     local out = {}
     local out = {}
Line 2,277: Line 2,293:
             local known_release = string.reverse(
             local known_release = string.reverse(
                 string.match(
                 string.match(
                     string.reverse(v['items._pageName']),  
                     string.reverse(v['items._pageName']),
                     '%)(.-)%('
                     '%)(.-)%('
                 )
                 )
             )  
             )
       
 
             local drop_enabled
             local drop_enabled
             if known_release == 'Original' then  
             if known_release == 'Original' then
                 drop_enabled = i18n.item_disambiguation.original
                 drop_enabled = i18n.item_disambiguation.original
             elseif m_util.cast.boolean(v['items.drop_enabled']) then
             elseif m_util.cast.boolean(v['items.drop_enabled']) then
                 drop_enabled = i18n.item_disambiguation.drop_enabled
                 drop_enabled = i18n.item_disambiguation.drop_enabled
             else  
             else
                 drop_enabled = i18n.item_disambiguation.drop_disabled
                 drop_enabled = i18n.item_disambiguation.drop_disabled
             end
             end
           
 
           
 
             if known_release ~= 'Original' then  
             if known_release ~= 'Original' then
                 known_release = string.format(
                 known_release = string.format(
                     i18n.item_disambiguation.known_release,  
                     i18n.item_disambiguation.known_release,
                     known_release,
                     known_release,
                     known_release
                     known_release
Line 2,301: Line 2,317:
                 known_release = ''
                 known_release = ''
             end
             end
           
 
             tbl
             tbl
                 :tag('li')
                 :tag('li')
                     :wikitext(
                     :wikitext(
                         string.format(
                         string.format(
                             i18n.item_disambiguation.list_pattern,  
                             i18n.item_disambiguation.list_pattern,
                             f_item_link{page=v['items._pageName']},
                             f_item_link{page=v['items._pageName']},
                             drop_enabled,
                             drop_enabled,
Line 2,315: Line 2,331:
     end
     end
     out[#out+1] = tostring(container)
     out[#out+1] = tostring(container)
   
 
     -- Add a category when the template uses old template inputs:
     -- Add a category when the template uses old template inputs:
     local old_args = {
     local old_args = {
Line 2,326: Line 2,342:
         'show_current',
         'show_current',
     }
     }
     for _,v in ipairs(old_args) do  
     for _,v in ipairs(old_args) do
         if tpl_args[v] ~= nil then
         if tpl_args[v] ~= nil then
             return table.concat(out, '') .. m_util.misc.add_category(
             return table.concat(out, '') .. m_util.misc.add_category(
Line 2,333: Line 2,349:
         end
         end
     end
     end
   
 
     return table.concat(out, '')  
     return table.concat(out, '')
end  
end




Line 2,341: Line 2,357:
     --[[
     --[[
     Creates a simple list of items.
     Creates a simple list of items.
   
 
     Examples
     Examples
     --------
     --------
     = p.simple_item_list{
     = p.simple_item_list{
         q_tables='maps',
         q_tables='maps',
         q_join='items._pageID=maps._pageID',  
         q_join='items._pageID=maps._pageID',
         q_where='maps.tier=1 AND items.drop_enabled=1 AND items.rarity_id="normal"',
         q_where='maps.tier=1 AND items.drop_enabled=1 AND items.rarity_id="normal"',
         no_html=1,
         no_html=1,
Line 2,352: Line 2,368:
     }
     }
     ]]
     ]]
   
 
     -- Args
     -- Args
     local tpl_args = getArgs(frame, {
     local tpl_args = getArgs(frame, {
Line 2,358: Line 2,374:
     })
     })
     frame = m_util.misc.get_frame(frame)
     frame = m_util.misc.get_frame(frame)
   
 
     local query = {}
     local query = {}
     for key, value in pairs(tpl_args) do  
     for key, value in pairs(tpl_args) do
         if string.sub(key, 0, 2) == 'q_' then
         if string.sub(key, 0, 2) == 'q_' then
             query[string.sub(key, 3)] = value
             query[string.sub(key, 3)] = value
         end
         end
     end
     end
   
 
     local fields = {
     local fields = {
         'items._pageName',
         'items._pageName',
Line 2,371: Line 2,387:
         'items.class',
         'items.class',
     }
     }
   
 
     if tpl_args.no_icon == nil then
     if tpl_args.no_icon == nil then
         fields[#fields+1] = 'items.inventory_icon'
         fields[#fields+1] = 'items.inventory_icon'
     end
     end
   
 
     if tpl_args.no_html == nil then
     if tpl_args.no_html == nil then
         fields[#fields+1] = 'items.html'
         fields[#fields+1] = 'items.html'
     end
     end
   
 
     local tables = m_util.string.split(tpl_args.q_tables or '', ',%s*')
     local tables = m_util.string.split(tpl_args.q_tables or '', ',%s*')
     table.insert(tables, 'items')
     table.insert(tables, 'items')
   
 
     query.groupBy = query.groupBy or 'items._pageID'
     query.groupBy = query.groupBy or 'items._pageID'
   
 
     local results = m_cargo.query(
     local results = m_cargo.query(
         tables,
         tables,
Line 2,390: Line 2,406:
         query
         query
     )
     )
       
 
     local out = {}
     local out = {}
     for _, row in ipairs(results) do
     for _, row in ipairs(results) do
Line 2,398: Line 2,414:
         else
         else
             page = row['items._pageName']
             page = row['items._pageName']
         end  
         end
       
 
         local link = f_item_link{
         local link = f_item_link{
             page=page,  
             page=page,
             name=row['items.name'],  
             name=row['items.name'],
             inventory_icon=row['items.inventory_icon'] or '',  
             inventory_icon=row['items.inventory_icon'] or '',
             html=row['items.html'] or '',  
             html=row['items.html'] or '',
             skip_query=true
             skip_query=true
         }
         }
       
 
         if tpl_args.format == nil then
         if tpl_args.format == nil then
             out[#out+1] = string.format('* %s', link)
             out[#out+1] = string.format('* %s', link)
Line 2,418: Line 2,434:
         end
         end
     end
     end
   
 
     if tpl_args.format == nil then
     if tpl_args.format == nil then
         return table.concat(out, '\n')
         return table.concat(out, '\n')
Line 2,436: Line 2,452:
     keys = {}
     keys = {}
     for _, data in ipairs(tbl) do
     for _, data in ipairs(tbl) do
         if type(data.arg) == 'string' then  
         if type(data.arg) == 'string' then
             keys[data.arg] = 1
             keys[data.arg] = 1
         elseif type(data.arg) == 'table' then
         elseif type(data.arg) == 'table' then
Line 2,444: Line 2,460:
         end
         end
     end
     end
   
 
     local out = {}
     local out = {}
     for key, _ in pairs(keys) do
     for key, _ in pairs(keys) do
         out[#out+1] = string.format("['%s'] = '1'", key)
         out[#out+1] = string.format("['%s'] = '1'", key)
     end
     end
   
 
     return table.concat(out, ', ')
     return table.concat(out, ', ')
end
end

Revision as of 19:11, 11 February 2020

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


This module is used on 4000+ pages.

To avoid major disruption and server load, do not make unnecessary edits to this module. Test changes to this module first using its /sandbox and /testcases subpages . All of the changes can then be applied to this module in a single edit.

Consider discussing changes on the talk page or on Discord before implementing them.

The item module provides functionality for creating item tables.

Implemented templates

This module implements the following templates:

ru:Модуль:Item table

-- Item table
--
-- Creates various item tables from cargo queries.
--
--
-- Todo list
-- ---------
-- * 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.


-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------
local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local f_item_link = require('Module:Item link').item_link
local 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 = {
    categories = {
        -- maintenance cats
        query_limit = 'Item tables hitting query limit',
        query_hard_limit = 'Item tables hitting hard query limit',
        no_results = 'Item tables without results',
    },

    -- Used by the item table
    item_table = {
        item = 'Item',
        skill_gem = 'Skill gem',

        physical_dps = m_util.html.abbr('pDPS', 'physical damage per second'),
        fire_dps = m_util.html.abbr('Fire DPS', 'fire damage per second'),
        cold_dps = m_util.html.abbr('Cold DPS', 'cold damage per second'),
        lightning_dps = m_util.html.abbr('Light. DPS', 'lightning damage per second'),
        chaos_dps = m_util.html.abbr('Chaos DPS', 'chaos damage per second'),
        elemental_dps = m_util.html.abbr('eDPS', 'elemental damage (i.e. fire/cold/lightning) per second'),
        poison_dps = m_util.html.abbr('Poison DPS', 'poison damage (i.e. physical/chaos) per second'),
        dps = m_util.html.abbr('DPS', 'total damage (i.e. physical/fire/cold/lightning/chaos) per second'),
        base_item = 'Base Item',
        metadata_id = 'Metadata ID',
        item_class = 'Item Class',
        rarity = 'Rarity',
        rarity_id = 'Rarity ID',
        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'),
        atlas_tier = 'Atlas map tier<br>based on [[atlas region|region]]',
        atlas_level = 'Atlas map level<br>based on [[atlas region|region]]',
        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 = {
        generic_argument_parameter = 'Unrecognized %s parameter "%s"',
        invalid_item_table_mode = 'Invalid mode for item table',
    },
}


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

local h = {}

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

h.tbl = {}

function h.tbl.range_fields(field)
    return function()
        local fields = {}
        for _, partial_field in ipairs({'maximum', 'text', 'colour'}) do
            fields[#fields+1] = string.format('%s_range_%s', field, partial_field)
        end
        return fields
    end
end

h.tbl.display = {}
function h.tbl.display.na_or_val(tr, value, data)
    return h.na_or_val(tr, value)
end

function h.tbl.display.seconds(tr, value, data)
    return h.na_or_val(tr, value, function(value)
        return string.format('%ss', value)
    end)
end

function h.tbl.display.percent(tr, value, data)
    return h.na_or_val(tr, value, function(value)
        return string.format('%s%%', value)
    end)
end

function h.tbl.display.wikilink(tr, value, data)
    return h.na_or_val(tr, value, function(value)
        return string.format('[[%s]]', value)
    end)
end

h.tbl.display.factory = {}
function h.tbl.display.factory.value(args)
    args.options = args.options or {}

    return function(tr, data, fields, data2)
        local values = {}
        local fmt_values = {}
        local sdata = data2.skill_levels[data['items._pageName']]

        for index, field in ipairs(fields) do
            local value = {
                min=data[field],
                max=data[field],
                base=data[field],
            }
            if sdata then
                value.min = value.min or sdata['0'][field] or sdata['1'][field]
                value.max = value.max or sdata['0'][field] or sdata[data['skill.max_level']][field]
            end
            if value.min then
                values[#values+1] = value.max
                local opts = args.options[index] or {}
                -- global colour is set, no overrides
                if args.colour ~= nil then
                    opts.no_color = true
                end
                fmt_values[#fmt_values+1] = m_util.html.format_value(nil, nil, value, opts)
            end
        end

        if #values == 0 then
            tr:wikitext(m_util.html.td.na())
        else
            local td = tr:tag('td')
            td:attr('data-sort-value', table.concat(values, ', '))
            td:wikitext(table.concat(fmt_values, ', '))
            if args.colour then
                td:attr('class', 'tc -' .. args.colour)
            end
        end
    end
end

function h.tbl.display.factory.range(args)
    -- args: table
    --  property
    return function (tr, data, fields)
        tr
            :tag('td')
                :attr('data-sort-value', data[string.format('%s_range_maximum', args.field)] or '0')
                :attr('class', 'tc -' .. (data[string.format('%s_range_colour', args.field)] or 'default'))
                :wikitext(data[string.format('%s_range_text', args.field)])
                :done()
    end
end

function h.tbl.display.factory.descriptor_value(args)
    -- Arguments:
    --  key
    --  tbl
    args = args or {}
    return function (tpl_args, frame, value)
        args.tbl = args.tbl or tpl_args
        if args.tbl[args.key] then
            value = m_util.html.abbr(value, args.tbl[args.key])
        end
        return value
    end
end

function h.tbl.display.factory.atlas_tier(args)
    args = args or {}
    return function (tr, data)
        for i=0,4 do
            local t = tonumber(data['atlas_maps.map_tier' .. i])

            if t == 0 then
                tr
                    :tag('td')
                        :attr('table-sort-value', 0)
                        :attr('class', 'table-cell-xmark')
                        :wikitext('✗')
            else
                if args.is_level then
                    t = t + 67
                end
                tr
                    :tag('td')
                        :attr('class', 'tc -value')
                        :wikitext(t)
                        :done()
            end
        end
    end
end

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

local data_map = {}

-- for sort type see:
-- https://meta.wikimedia.org/wiki/Help:Sorting
data_map.generic_item = {
    {
        arg = 'base_item',
        header = i18n.item_table.base_item,
        fields = {'items.base_item', 'items.base_item_page'},
        display = function(tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['items.base_item'])
                    :wikitext(string.format('[[%s|%s]]', data['items.base_item_page'], data['items.base_item']))
        end,
        order = 1000,
        sort_type = 'text',
    },
    {
        arg = 'class',
        header = i18n.item_table.item_class,
        fields = {'items.class'},
        display = h.tbl.display.factory.value{options = {
            [1] = {
                fmt='[[%s]]',
            },
        }},
        order = 1001,
        sort_type = 'text',
    },
    {
        arg = 'rarity',
        header = i18n.item_table.rarity,
        fields = {'items.rarity'},
        display = h.tbl.display.factory.value{},
        order = 1002,
    },
    {
        arg = 'rarity_id',
        header = i18n.item_table.rarity_id,
        fields = {'items.rarity_id'},
        display = h.tbl.display.factory.value{},
        order = 1003,
    },
    {
        arg = 'metadata_id',
        header = i18n.item_table.metadata_id,
        fields = {'items.metadata_id'},
        display = h.tbl.display.factory.value{},
        order = 1004,
    },
    {
        arg = 'essence',
        header = i18n.item_table.essence_level,
        fields = {'essences.level'},
        display = h.tbl.display.factory.value{},
        order = 2000,
    },
    {
        arg = {'drop', 'drop_level'},
        header = i18n.item_table.drop_level,
        fields = {'items.drop_level'},
        display = h.tbl.display.factory.value{},
        order = 3000,
    },
    {
        arg = 'stack_size',
        header = i18n.item_table.stack_size,
        fields = {'stackables.stack_size'},
        display = h.tbl.display.factory.value{},
        order = 4000,
    },
    {
        arg = 'stack_size_currency_tab',
        header = i18n.item_table.stack_size_currency_tab,
        fields = {'stackables.stack_size_currency_tab'},
        display = h.tbl.display.factory.value{},
        order = 4001,
    },
    {
        arg = 'level',
        header = m_game.level_requirement.icon,
        fields = h.tbl.range_fields('items.required_level'),
        display = h.tbl.display.factory.range{field='items.required_level'},
        order = 5000,
    },
    {
        arg = 'ar',
        header = i18n.item_table.armour,
        fields = h.tbl.range_fields('armours.armour'),
        display = h.tbl.display.factory.range{field='armours.armour'},
        order = 6000,
    },
    {
        arg = 'ev',
        header =i18n.item_table.evasion,
        fields = h.tbl.range_fields('armours.evasion'),
        display = h.tbl.display.factory.range{field='armours.evasion'},
        order = 6001,
    },
    {
        arg = 'es',
        header = i18n.item_table.energy_shield,
        fields = h.tbl.range_fields('armours.energy_shield'),
        display = h.tbl.display.factory.range{field='armours.energy_shield'},
        order = 6002,
    },
    {
        arg = 'block',
        header = i18n.item_table.block,
        fields = h.tbl.range_fields('shields.block'),
        display = h.tbl.display.factory.range{field='shields.block'},
        order = 6003,
    },
    --[[{
        arg = 'physical_damage_min',
        header = m_util.html.abbr('Min', 'Local minimum weapon damage'),
        fields = h.tbl.range_fields('minimum physical damage'),
        display = h.tbl.display.factory.range{field='minimum physical damage'},
        order = 7000,
    },
    {
        arg = 'physical_damage_max',
        header = m_util.html.abbr('Max', 'Local maximum weapon damage'),
        fields = h.tbl.range_fields('maximum physical damage'),
        display = h.tbl.display.factory.range{field='maximum physical damage'},
        order = 7001,

    },]]--
    {
        arg = {'weapon', 'damage'},
        header = i18n.item_table.damage,
        fields = {'weapons.damage_html', 'weapons.damage_avg'},
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['weapons.damage_avg'])
                    :wikitext(data['weapons.damage_html'])
        end,
        order = 8000,
    },
    {
        arg = {'weapon', 'aps'},
        header = i18n.item_table.attacks_per_second,
        fields = h.tbl.range_fields('weapons.attack_speed'),
        display = h.tbl.display.factory.range{field='weapons.attack_speed'},
        order = 8001,
    },
    {
        arg = {'weapon', 'crit'},
        header = i18n.item_table.local_critical_strike_chance,
        fields = h.tbl.range_fields('weapons.critical_strike_chance'),
        display = h.tbl.display.factory.range{field='weapons.critical_strike_chance'},
        order = 8002,
    },
    {
        arg = {'physical_dps'},
        header = i18n.item_table.physical_dps,
        fields = h.tbl.range_fields('weapons.physical_dps'),
        display = h.tbl.display.factory.range{field='weapons.physical_dps'},
        order = 8100,
    },
    {
        arg = {'lightning_dps'},
        header = i18n.item_table.lightning_dps,
        fields = h.tbl.range_fields('weapons.lightning_dps'),
        display = h.tbl.display.factory.range{field='weapons.lightning_dps'},
        order = 8101,
    },
    {
        arg = {'cold_dps'},
        header = i18n.item_table.cold_dps,
        fields = h.tbl.range_fields('weapons.cold_dps'),
        display = h.tbl.display.factory.range{field='weapons.cold_dps'},
        order = 8102,
    },
    {
        arg = {'fire_dps'},
        header = i18n.item_table.fire_dps,
        fields = h.tbl.range_fields('weapons.fire_dps'),
        display = h.tbl.display.factory.range{field='weapons.fire_dps'},
        order = 8103,
    },
    {
        arg = {'chaos_dps'},
        header = i18n.item_table.chaos_dps,
        fields = h.tbl.range_fields('weapons.chaos_dps'),
        display = h.tbl.display.factory.range{field='weapons.chaos_dps'},
        order = 8104,
    },
    {
        arg = {'elemental_dps'},
        header = i18n.item_table.elemental_dps,
        fields = h.tbl.range_fields('weapons.elemental_dps'),
        display = h.tbl.display.factory.range{field='weapons.elemental_dps'},
        order = 8105,
    },
    {
        arg = {'poison_dps'},
        header = i18n.item_table.poison_dps,
        fields = h.tbl.range_fields('weapons.poison_dps'),
        display = h.tbl.display.factory.range{field='weapons.poison_dps'},
        order = 8106,
    },
    {
        arg = {'dps'},
        header = i18n.item_table.dps,
        fields = h.tbl.range_fields('weapons.dps'),
        display = h.tbl.display.factory.range{field='weapons.dps'},
        order = 8107,
    },
    {
        arg = 'flask_life',
        header = i18n.item_table.flask_life,
        fields = h.tbl.range_fields('flasks.life'),
        display = h.tbl.display.factory.range{field='flasks.life'},
        order = 9000,
    },
    {
        arg = 'flask_mana',
        header = i18n.item_table.flask_mana,
        fields = h.tbl.range_fields('flasks.mana'),
        display = h.tbl.display.factory.range{field='flasks.mana'},
        order = 9001,
    },
    {
        arg = 'flask',
        header = i18n.item_table.flask_duration,
        fields = h.tbl.range_fields('flasks.duration'),
        display = h.tbl.display.factory.range{field='flasks.duration'},
        order = 9002,
    },
    {
        arg = 'flask',
        header = i18n.item_table.flask_charges_per_use,
        fields = h.tbl.range_fields('flasks.charges_per_use'),
        display = h.tbl.display.factory.range{field='flasks.charges_per_use'},
        order = 9003,
    },
    {
        arg = 'flask',
        header = i18n.item_table.flask_maximum_charges,
        fields = h.tbl.range_fields('flasks.charges_max'),
        display = h.tbl.display.factory.range{field='flasks.charges_max'},
        order = 9004,
    },
    {
        arg = 'item_limit',
        header = i18n.item_table.item_limit,
        fields = {'jewels.item_limit'},
        display = h.tbl.display.factory.value{},
        order = 10000,
    },
    {
        arg = 'jewel_radius',
        header = i18n.item_table.jewel_radius,
        fields = {'jewels.radius_html'},
        display = function (tr, data)
            tr
                :tag('td')
                    :wikitext(data['jewels.radius_html'])
        end,
        order = 10001,
    },
    {
        arg = 'map_tier',
        header = i18n.item_table.map_tier,
        fields = {'maps.tier'},
        display = h.tbl.display.factory.value{},
        order = 11000,
    },
    {
        arg = 'map_level',
        header = i18n.item_table.map_level,
        fields = {'maps.area_level'},
        display = h.tbl.display.factory.value{},
        order = 11010,
    },
    {
        arg = 'map_guild_character',
        header = i18n.item_table.map_guild_character,
        fields = {'maps.guild_character'},
        display = h.tbl.display.factory.value{},
        order = 11020,
        sort_type = 'text',
    },
    {
        arg = 'atlas_tier',
        header = i18n.item_table.atlas_tier,
        colspan = 5,
        fields = {'atlas_maps.map_tier0', 'atlas_maps.map_tier1', 'atlas_maps.map_tier2', 'atlas_maps.map_tier3', 'atlas_maps.map_tier4'},
        display = h.tbl.display.factory.atlas_tier{is_level=false},
        order = 11050,
    },
    {
        arg = 'atlas_level',
        header = i18n.item_table.atlas_level,
        colspan = 5,
        fields = {'atlas_maps.map_tier0', 'atlas_maps.map_tier1', 'atlas_maps.map_tier2', 'atlas_maps.map_tier3', 'atlas_maps.map_tier4'},
        display = h.tbl.display.factory.atlas_tier{is_level=true},
        order = 11060,
    },
    {
        arg = {'doodad', 'master_level_requirement'},
        header = i18n.item_table.master_level_requirement,
        fields = {'hideout_doodads.level_requirement'},
        display = h.tbl.display.factory.value{},
        order = 11100,
    },
    {
        arg = {'doodad', 'master'},
        header = i18n.item_table.master,
        fields = {'hideout_doodads.master'},
        display = h.tbl.display.factory.value{},
        order = 11101,
        sort_type = 'text',
    },
    {
        arg = {'doodad', 'master_favour_cost'},
        header = i18n.item_table.master_favour_cost,
        fields = {'hideout_doodads.favour_cost'},
        display = h.tbl.display.factory.value{colour='currency'},
        order = 11102,
    },
    {
        arg = {'doodad', 'variation_count'},
        header = i18n.item_table.variation_count,
        fields = {'hideout_doodads.variation_count'},
        display = h.tbl.display.factory.value{colour='mod'},
        order = 11105,
    },
    {
        arg = 'buff',
        header = i18n.item_table.buff_effects,
        fields = {'item_buffs.stat_text'},
        display = h.tbl.display.factory.value{colour='mod'},
        order = 12000,
        sort_type = 'text',
    },
    {
        arg = 'stat',
        header = i18n.item_table.stats,
        fields = {'items.stat_text'},
        display = h.tbl.display.factory.value{colour='mod'},
        order = 12001,
        sort_type = 'text',
    },
    {
        arg = 'description',
        header = i18n.item_table.effects,
        fields = {'items.description'},
        display = h.tbl.display.factory.value{colour='mod'},
        order = 12002,
        sort_type = 'text',
    },
    {
        arg = 'incubator_effect',
        header = i18n.item_table.incubator_effect,
        fields = {'incubators.effect'},
        display = h.tbl.display.factory.value{colour='crafted'},
        order = 12004,
        sort_type = 'text',
    },
    {
        arg = 'flavour_text',
        header = i18n.item_table.flavour_text,
        fields = {'items.flavour_text'},
        display = h.tbl.display.factory.value{colour='flavour'},
        order = 12006,
        sort_type = 'text',
    },
    {
        arg = 'help_text',
        header = i18n.item_table.help_text,
        fields = {'items.help_text'},
        display = h.tbl.display.factory.value{colour='help'},
        order = 12008,
        sort_type = 'text',
    },
    {
        arg = {'prophecy', 'objective'},
        header = i18n.item_table.objective,
        fields = {'prophecies.objective'},
        display = h.tbl.display.factory.value{},
        order = 13002,
    },
        {
        arg = {'prophecy', 'reward'},
        header = i18n.item_table.reward,
        fields = {'prophecies.reward'},
        display = h.tbl.display.factory.value{},
        order = 13001,
    },
    {
        arg = {'prophecy', 'seal_cost'},
        header = i18n.item_table.seal_cost,
        fields = {'prophecies.seal_cost'},
        display = h.tbl.display.factory.value{colour='currency'},
        order = 13002,
    },
    {
        arg = {'prediction_text'},
        header = i18n.item_table.prediction_text,
        fields = {'prophecies.prediction_text'},
        display = h.tbl.display.factory.value{colour='value'},
        order = 12004,
        sort_type = 'text',
    },
    {
        arg = 'buff_icon',
        header = i18n.item_table.buff_icon,
        fields = {'item_buffs.icon'},
        display = h.tbl.display.factory.value{options = {
            [1] = {
                fmt='[[%s]]',
            },
        }},
        order = 14000,
        sort_type = 'text',
    },
    {
        arg = {'version', 'release_version'},
        header = i18n.item_table.release_version,
        fields = {'items.release_version'},
        display = function(tr, data)
            tr
                :tag('td')
                    :wikitext(
                        string.format(
                            i18n.item_table.version_link,
                            data['items.release_version'],
                            data['items.release_version']
                        )
                    )
        end,
        order = 15000,
    },
    {
        arg = {'version', 'removal_version'},
        header = i18n.item_table.removal_version,
        fields = {'items.removal_version'},
        display = function(tr, data)
            tr
                :tag('td')
                    :wikitext(
                        string.format(
                            i18n.item_table.version_link,
                            data['items.removal_version'],
                            data['items.removal_version']
                        )
                    )
        end,
        order = 15001,
    },
    {
        arg = {'drop', 'drop_enabled'},
        header = i18n.item_table.drop_enabled,
        fields = {'items.drop_enabled'},
        display = h.tbl.display.factory.value{},
        order = 15002,
    },
    {
        arg = {'drop', 'drop_leagues'},
        header = i18n.item_table.drop_leagues,
        fields = {'items.drop_leagues'},
        display = function(tr, data)
            local s = m_util.string.split(data['items.drop_leagues'], ',')
            for i, v in ipairs(s) do
                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'},
        header = i18n.item_table.drop_areas,
        fields = {'items.drop_areas_html'},
        display = h.tbl.display.factory.value{},
        order = 15004,
        sort_type = 'text',
    },
    {
        arg = {'drop', 'drop_text'},
        header = i18n.item_table.drop_text,
        fields = {'items.drop_text'},
        display = h.tbl.display.factory.value{},
        order = 15005,
        sort_type = 'text',
    },
    {
        arg = {'quest'},
        header = i18n.item_table.quest_rewards,
        fields = {'items._pageName'},
        display = function(tr, data, na, results2)
            if results2['quest_query'] == nil then
                results2['quest_query'] = m_cargo.query(
                    {'items', 'quest_rewards'},
                    {
                        'quest_rewards.reward',
                        'quest_rewards.classes',
                        'quest_rewards.act',
                        'quest_rewards.quest'
                    },
                    {
                        join='items._pageName=quest_rewards.reward',
                        where=string.format(
                            'items._pageID IN (%s) AND quest_rewards.reward 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.reward',
                }
            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 '', '�'), ', ')
                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',
    },
    {
        arg = {'vendor'},
        header = i18n.item_table.vendor_rewards,
        fields = {'items._pageName'},
        display = function(tr, data, na, results2)
            if results2['vendor_query'] == nil then
                results2['vendor_query'] = m_cargo.query(
                    {'items', 'vendor_rewards'},
                    {
                        'vendor_rewards.reward',
                        'vendor_rewards.classes',
                        'vendor_rewards.act',
                        'vendor_rewards.npc',
                        'vendor_rewards.quest',
                    },
                    {
                        join='items._pageName=vendor_rewards.reward',
                        where=string.format(
                            'items._pageID IN (%s) AND vendor_rewards.reward 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.reward',
                }
            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 '', '�'), ', ')
                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

            value = table.concat(tbl, '<br>')
            if value == nil or value == '' then
                tr:wikitext(m_util.html.td.na())
            else
                tr
                    :tag('td')
                        :attr('style', 'text-align:left')
                        :wikitext(value)
            end
        end,
        order = 17001,
        sort_type = 'text',
    },
    {
        arg = {'price', 'purchase_cost'},
        header = i18n.item_table.purchase_costs,
        fields = {'item_purchase_costs.name', 'item_purchase_costs.amount'},
        display = function (tr, data)
            -- Can purchase costs have multiple currencies and rows?
            -- Just switch to the same method as in sell_price then.
            local tbl = {}
            if data['item_purchase_costs.name'] ~= nil then
                tbl[#tbl+1] = string.format(
                        '%sx %s',
                        data['item_purchase_costs.amount'],
                        f_item_link{data['item_purchase_costs.name']}
                    )
            end
            h.na_or_val(tr, table.concat(tbl, '<br>'))
        end,
        order = 18001,
        sort_type = 'text',
    },
    {
        arg = {'price', 'sell_price'},
        header = i18n.item_table.sell_price,
        fields = {'item_sell_prices.name', 'item_sell_prices.amount'},
        display = function(tr, data, na, results2)
            if results2['sell_price_query'] == nil then
                results2['sell_price_query'] = m_cargo.query(
                    {'items', 'item_sell_prices'},
                    {
                        'item_sell_prices.name',
                        'item_sell_prices.amount',
                        'item_sell_prices._pageID'
                    },
                    {
                        join='items._pageID=item_sell_prices._pageID',
                        where=string.format(
                            'items._pageID IN (%s) AND item_sell_prices._pageID IS NOT NULL',
                            table.concat(results2.pageIDs, ', ')
                        ),
                        orderBy='item_sell_prices.name',
                    }
                )
                results2['sell_price_query'] = m_cargo.map_results_to_id{
                    results=results2['sell_price_query'],
                    field='item_sell_prices._pageID',
                }
            end
            local results = results2['sell_price_query'][data['items._pageID']] or {}

            local tbl = {}
            for _,v in ipairs(results) do
                tbl[#tbl+1] = string.format(
                    '%sx %s',
                    v['item_sell_prices.amount'],
                    f_item_link{v['item_sell_prices.name']}
                )
            end
            h.na_or_val(tr, table.concat(tbl, '<br>'))
        end,
        order = 18002,
        sort_type = 'text',
    },
    {
        arg = {'boss', 'boss_name'},
        header = i18n.item_table.boss_name,
        fields = {'maps.area_id'},
        display = function(tr, data, na, results2)
            if results2['boss_query'] == nil then
                results2['boss_query'] = m_cargo.query(
                    {'items', 'maps', 'areas', 'monsters', 'main_pages'},
                    {
                        'items._pageName',
                        'maps.area_id',
                        'areas.id',
                        'areas.boss_monster_ids',
                        'monsters._pageName',
                        'monsters.name',
                        'main_pages._pageName',
                    },
                    {
                        join=[[
                            items._pageID=maps._pageID,
                            maps.area_id=areas.id,
                            areas.boss_monster_ids HOLDS monsters.metadata_id,
                            monsters.metadata_id=main_pages.id
                        ]],
                        where=string.format([[
                            items._pageID IN (%s)
                            AND maps.area_id IS NOT NULL
                            AND areas.boss_monster_ids HOLDS LIKE "%%"
                        ]],
                            table.concat(results2.pageIDs, ', ')
                        ),
                        orderBy='areas.boss_monster_ids',
                    }
                )

                results2['boss_query'] = m_cargo.map_results_to_id{
                    results=results2['boss_query'],
                    field='items._pageName',
                }
            end
            local results = results2['boss_query'][data['items._pageName']] or {}
            local tbl = {}
            for _,v in ipairs(results) do
                local page = v['main_pages._pageName'] or v['monsters._pageName'] or ''
                local name = v['monsters.name'] or v['areas.boss_monster_ids'] or ''
                tbl[#tbl+1] = string.format('[[%s|%s]]', page, name)
            end
            h.na_or_val(tr, table.concat(tbl, '<br>'))
        end,
        order = 19001,
        sort_type = 'text',
    },
    {
        arg = {'boss', 'boss_number'},
        header = i18n.item_table.boss_number,
        fields = {'maps.area_id'},
        display = function(tr, data, na, results2)
            if results2['boss_query'] == nil then
                results2['boss_query'] = m_cargo.query(
                    {'items', 'maps', 'areas', 'monsters', 'main_pages'},
                    {
                        'items._pageName',
                        'maps.area_id',
                        'areas.id',
                        'areas.boss_monster_ids',
                        'monsters._pageName',
                        'monsters.name',
                        'main_pages._pageName',
                    },
                    {
                        join=[[
                            items._pageID=maps._pageID,
                            maps.area_id=areas.id,
                            areas.boss_monster_ids HOLDS monsters.metadata_id,
                            monsters.metadata_id=main_pages.id
                        ]],
                        where=string.format([[
                            items._pageID IN (%s)
                            AND maps.area_id IS NOT NULL
                            AND areas.boss_monster_ids HOLDS LIKE "%%"
                        ]],
                            table.concat(results2.pageIDs, ', ')
                        ),
                        orderBy='areas.boss_monster_ids',
                    }
                )

                results2['boss_query'] = m_cargo.map_results_to_id{
                    results=results2['boss_query'],
                    field='items._pageName',
                }
            end
            local results = results2['boss_query'][data['items._pageName']] or {}
            local tbl = {}
            for _,v in ipairs(results) do
                tbl[#tbl+1] = v['areas.boss_monster_ids']
            end
            h.na_or_val(tr, #tbl)
        end,
        order = 19002,
    },
    {
        arg = {'sextant'},
        header = i18n.item_table.sextant,
        fields = {'items.name', 'atlas_maps.x', 'atlas_maps.y', 'maps.series'},
        display = function(tr, data, na, results2)
            if results2['sextant_query'] == nil then
                results2['sextant_query'] = m_cargo.query(
                    {'items', 'maps', 'atlas_maps'},
                    {
                        'items._pageName',
                        'items.name',
                        'maps.series',
                        'atlas_maps.x',
                        'atlas_maps.y',
                        'atlas_maps.connections'
                    },
                    {
                        join='items._pageID=atlas_maps._pageID, items._pageID=maps._pageID, maps._pageID=atlas_maps._pageID',
                        where='atlas_maps.x IS NOT NULL',
                        orderBy='maps.tier, items._pageName',
                    }
                )
            end
            local results = results2['sextant_query'] or {}

            local sextant_radius = 55 -- Should be in Module:Game?
            local x_center = data['atlas_maps.x']
            local y_center = data['atlas_maps.y']

            if not (x_center and y_center) then
                return
            end

            local tbl = {}
            for _, v in ipairs(results) do
                local x = v['atlas_maps.x']
                local y = v['atlas_maps.y']
                local r = ((x-x_center)^2 + (y-y_center)^2)^0.5
                if (sextant_radius >= r) and (data['items._pageName'] ~= v['items._pageName']) then --
                    tbl[#tbl+1] = f_item_link({page=v['items._pageName']})
                end
            end
            h.na_or_val(tr, table.concat(tbl, '<br>'))
        end,
        order = 20001,
        sort_type = 'text',
    },
    {
        arg = {'legacy'},
        header = i18n.item_table.legacy,
        fields = {'items.name'},
        display = function(tr, data, na, results2)
            if results2['legacy_query'] == nil then
                results2['legacy_query'] = m_cargo.query(
                    {'items', 'legacy_variants'},
                    {
                        'items._pageID',
                        'items._pageName',
                        'items.frame_type',
                        'legacy_variants.removal_version',
                        'legacy_variants.implicit_stat_text',
                        'legacy_variants.explicit_stat_text',
                        'legacy_variants.stat_text',
                        'legacy_variants.base_item',
                        'legacy_variants.required_level'
                    },
                    {
                        join='items._pageID=legacy_variants._pageID',
                        where='legacy_variants.removal_version IS NOT NULL',
                        where=string.format(
                            'items._pageID IN (%s) AND legacy_variants.removal_version IS NOT NULL',
                            table.concat(results2.pageIDs, ', ')
                        ),
                        orderBy='items._pageName',
                    }
                )

                results2['legacy_query'] = m_cargo.map_results_to_id{
                    results=results2['legacy_query'],
                    field='items._pageName',
                }
            end
            local results = results2['legacy_query'][data['items._pageName']] or {}

            local tbl = mw.html.create('table')
                :attr('width', '100%')
            for _, v in ipairs(results) do
                local cell = {}
                local l = {
                    'legacy_variants.base_item',
                    'legacy_variants.stat_text'
                }

                -- Clean up data:
                for _, k in ipairs(l) do
                    if v[k] ~= nil then
                        local s = m_util.string.split(v[k], '*')
                        local s_flt = {}
                        for _, sss in ipairs(s) do
                            if sss ~= nil and sss ~= '' then
                                s_flt[#s_flt+1] = string.gsub(sss, '\n', '')
                            end
                        end

                        cell[#cell+1] = table.concat(s_flt, '<br>')
                    end
                end

                local sep = string.format(
                    '<span class="item-stat-separator -%s"></span>',
                    v['items.frame_type']
                )
                tbl
                    :tag('tr')
                        :attr('class', 'upgraded-from-set')
                        :tag('td')
                            :wikitext(
                                v['legacy_variants.removal_version']
                            )
                            :done()
                        :tag('td')
                            :attr('class', 'group legacy-stats plainlist')
                            :wikitext(table.concat(cell, sep))
                            :done()
                    :done()
            end
            tr
                :tag('td')
                    :node(tbl)
        end,
        order = 21001,
        sort_type = 'text',
    },
    {
        arg = {'granted_skills'},
        header = i18n.item_table.granted_skills,
        fields = {'items.name'},
        display = function(tr, data, na, results2)
            if results2['granted_skills_query'] == nil then
                results2['granted_skills_query'] = m_cargo.query(
                    {'items', 'item_mods', 'mods', 'skill', 'items=items2'},
                    {
                        'items._pageName',
                        'items.name',
                        'item_mods.id',
                        'mods._pageName',
                        'mods.id',
                        'mods.granted_skill',
                        'mods.stat_text_raw',
                        'skill._pageName',
                        'skill.skill_id',
                        'skill.active_skill_name',
                        'skill.skill_icon',
                        'skill.stat_text',
                        'items2.class',
                        'items2.name',
                        'items2.inventory_icon',
                        'items2.size_x',
                        'items2.size_y',
                        'items2.html',
                    },
                    {
                        join='items._pageID=item_mods._pageID, item_mods.id=mods.id, mods.granted_skill=skill.skill_id, skill._pageID=items2._pageID',
                        where=string.format(
                            'items._pageID IN (%s) AND mods.granted_skill IS NOT NULL',
                            table.concat(results2.pageIDs, ', ')
                        ),
                    }
                )

                results2['granted_skills_query'] = m_cargo.map_results_to_id{
                    results=results2['granted_skills_query'],
                    field='items._pageName',
                }
            end
            local results = results2['granted_skills_query'][data['items._pageName']] or {}

            local tbl = {}
            for _, v in ipairs(results) do

                -- Check if a level for the skill is specified in the
                -- mod stat text.
                -- Stat ids have unreliable naming convention so using
                -- the mod stat text instead.
                local level = ''
                local stat_text = v['mods.stat_text_raw'] or ''
                local level_number = string.match(
                    stat_text:lower(),
                    m_util.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 = m_util.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] = 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
            h.na_or_val(tr, table.concat(tbl, '<br>'))
        end,
        order = 22001,
        sort_type = 'text',
    },
    {
        arg = 'alternate_art',
        header = i18n.item_table.alternate_art,
        fields = {'items.alternate_art_inventory_icons'},
        display = function (tr, data)
            local alt_art = m_util.string.split(
                data['items.alternate_art_inventory_icons'],
                ','
            )

            -- TODO: Use il instead to handle size?
            -- local size = 39
            local out = {}
            for i,v in ipairs(alt_art) do
                out[#out+1] = string.format(
                    '[[%s|link=|%s]]',
                    v,
                    v
                )
            end

            tr
                :tag('td')
                    :wikitext(table.concat(out, ''))
        end,
        order = 23000,
        sort_type = 'text',
    },
}

data_map.skill_gem_new = {
    {
        arg = 'icon',
        header = i18n.item_table.support_gem_letter,
        fields = {'skill_gems.support_gem_letter_html'},
        display = h.tbl.display.factory.value{},
        order = 1000,
        sort_type = 'text',
    },
    {
        arg = 'skill_icon',
        header = i18n.item_table.skill_icon,
        fields = {'skill.skill_icon'},
        display = h.tbl.display.factory.value{options = {
            [1] = {
                fmt='[[%s]]',
            },
        }},
        order = 1001,
        sort_type = 'text',
    },
    {
        arg = {'stat', 'stat_text'},
        header = i18n.item_table.stats,
        fields = {'skill.stat_text'},
        display = h.tbl.display.factory.value{},
        order = 2000,
        sort_type = 'text',
    },
    {
        arg = {'quality', 'quality_stat_text'},
        header = i18n.item_table.quality_stats,
        fields = {'skill.quality_stat_text'},
        display = h.tbl.display.factory.value{},
        order = 2001,
        sort_type = 'text',
    },
    {
        arg = 'description',
        header = i18n.item_table.description,
        fields = {'skill.description'},
        display = h.tbl.display.factory.value{},
        order = 2100,
        sort_type = 'text',
    },
    {
        arg = 'level',
        header = m_game.level_requirement.icon,
        fields = h.tbl.range_fields('items.required_level'),
        display = h.tbl.display.factory.range{field='items.required_level'},
        order = 3004,
    },
    {
        arg = 'crit',
        header = i18n.item_table.skill_critical_strike_chance,
        fields = {'skill_levels.critical_strike_chance'},
        display = h.tbl.display.factory.value{options = {
            [1] = {
                fmt='%s%%',
                skill_levels = true,
            },
        }},
        order = 4000,
        options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        arg = 'cast_time',
        header = i18n.item_table.cast_time,
        fields = {'skill.cast_time'},
        display = h.tbl.display.factory.value{options = {
        }},
        order = 4001,
        options = {
        },
    },
    {
        arg = {'aspd', 'attack_speed', 'attack_speed_multiplier'},
        header = i18n.item_table.attack_speed_multiplier,
        fields = {'skill_levels.attack_speed_multiplier'},
        display = h.tbl.display.factory.value{options = {
            [1] = {
                fmt='%s%%',
                skill_levels = true,
            },
        }},
        order = 4002,
        options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        arg = 'dmgeff',
        header = i18n.item_table.damage_effectiveness,
        fields = {'skill_levels.damage_effectiveness'},
        display = h.tbl.display.factory.value{options = {
            [1] = {
                fmt='%s%%',
                skill_levels = true,
            },
        }},
        order = 4003,
        options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        arg = 'mcm',
        header = i18n.item_table.mana_cost_multiplier,
        fields = {'skill_levels.mana_multiplier'},
        display = h.tbl.display.factory.value{options = {
            [1] = {
                fmt='%s%%',
                skill_levels = true,
            },
        }},
        order = 5000,
        options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        arg = 'mana',
        header = i18n.item_table.mana_cost,
        fields = {'skill_levels.mana_cost', 'skill.has_percentage_mana_cost', 'skill.has_reservation_mana_cost'},
        display = function (tr, data, fields, data2)
            local appendix = ''
            if m_util.cast.boolean(data['skill.has_percentage_mana_cost']) then
                appendix = appendix .. '%%'
            end
            if m_util.cast.boolean(data['skill.has_reservation_mana_cost']) then
                appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
            end

            h.tbl.display.factory.value{options = {
                [1] = {
                    fmt='%d' .. appendix,
                    skill_levels = true,
                },
            }}(tr, data, {'skill_levels.mana_cost'}, data2)
        end,
        order = 5001,
        options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        arg = 'vaal',
        header = i18n.item_table.vaal_souls_requirement,
        fields = {'skill_levels.vaal_souls_requirement'},
        display = h.tbl.display.factory.value{options = {
            [1] = {
                skill_levels = true,
            },
        }},
        order = 6000,
        options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        arg = 'vaal',
        header = i18n.item_table.stored_uses,
        fields = {'skill_levels.vaal_stored_uses'},
        display = h.tbl.display.factory.value{options = {
            [1] = {
                skill_levels = true,
            },
        }},
        order = 6001,
        options = {
            [1] = {
                skill_levels = true,
            },
        },
    },
    {
        arg = 'radius',
        header = i18n.item_table.primary_radius,
        fields = {'skill.radius', 'skill.radius_description'},
        options = {[2] = {optional = true}},
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['skill.radius'])
                    :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_description'}(nil, nil, data['skill.radius']))
        end,
        order = 7000,
    },
    {
        arg = 'radius',
        header = i18n.item_table.secondary_radius,
        fields = {'skill.radius_secondary', 'skill.radius_secondary_description'},
        options = {[2] = {optional = true}},
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['skill.radius_secondary'])
                    :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_secondary_description'}(nil, nil, data['skill.radius_secondary']))
        end,
        order = 7001,
    },
    {
        arg = 'radius',
        header = i18n.item_table.tertiary_radius,
        fields = {'skill.radius_tertiary', 'skill.radius_tertiary_description'},
        options = {[2] = {optional = true}},
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['skill.radius_tertiary'])
                   :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_tertiary_description'}(nil, nil, data['skill.radius_tertiary']))
        end,
        order = 7002,
    },
}

for i, attr in ipairs(m_game.constants.attribute_order) do
    local attr_data = m_game.constants.attributes[attr]
    table.insert(data_map.generic_item, 7, {
        arg = attr_data.arg,
        header = attr_data.icon,
        fields = h.tbl.range_fields(string.format('items.required_%s', attr)),
        display = h.tbl.display.factory.range{field=string.format('items.required_%s', attr)},
        order = 5000+i,
    })
    table.insert(data_map.skill_gem_new, 1, {
        arg = attr_data.arg,
        header = attr_data.icon,
        fields = {string.format('skill_gems.%s_percent', attr)},
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data[string.format('skill_gems.%s_percent', attr)])
                    :wikitext('[[File:Yes.png|yes|link=]]')
        end,
        order = 3000+i,
    })
end


-- ----------------------------------------------------------------------------
-- Invoke callables
-- ----------------------------------------------------------------------------

local p = {}

--
-- Template:Item table
--

function p.item_table(frame)
    --[[
    Creates a generic table for items.

    Examples
    --------
    = p.item_table{
        q_tables = 'items, vendor_rewards',
        q_join = 'items.name = vendor_rewards.reward',
        q_where= 'vendor_rewards.reward IS NOT NULL AND (items.class = "Active Skill Gems" OR items.class = "Support Skill Gems")',
        vendor=1,
    }

    ]]

    local t = os.clock()
    -- args
    local tpl_args = getArgs(frame, {
            parentFirst = true
        })
    frame = m_util.misc.get_frame(frame)

    tpl_args.q_where = m_cargo.replace_holds{string=tpl_args.q_where}

    local modes = {
        skill = {
            data = data_map.skill_gem_new,
            header = i18n.item_table.skill_gem,
        },
        item = {
            data = data_map.generic_item,
            header = i18n.item_table.item,
        },
    }

    if tpl_args.mode == nil then
        tpl_args.mode = 'item'
    end

    if modes[tpl_args.mode] == nil then
        error(i18n.errors.invalid_item_table_mode)
    end

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

    local row_infos = {}
    for _, row_info in ipairs(modes[tpl_args.mode].data) do
        local enabled = false
        if row_info.arg == nil then
            enabled = true
        elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then
            enabled = true
        elseif type(row_info.arg) == 'table' then
            for _, argument in ipairs(row_info.arg) do
                if m_util.cast.boolean(tpl_args[argument]) then
                    enabled = true
                    break
                end
            end
        end

        if enabled then
            row_info.options = row_info.options or {}
            row_infos[#row_infos+1] = row_info
        end
    end

    -- Parse stat arguments
    local stat_columns = {}
    local query_stats = {}
    local i = 0
    repeat
        i = i + 1

        local prefix = string.format('stat_column%s_', i)
        local col_info = {
            header = tpl_args[prefix .. 'header'] or tostring(i),
            format = tpl_args[prefix .. 'format'],
            stat_format = tpl_args[prefix .. 'stat_format'] or 'separate',
            order = tonumber(tpl_args[prefix .. 'order']) or (10000000 + i),
            stats = {},
            options = {},
        }

        local j = 0
        repeat
            j = j +1

            local stat_info = {
                id = tpl_args[string.format('%sstat%s_id', prefix, j)],
            }

            if stat_info.id then
                col_info.stats[#col_info.stats+1] = stat_info
                query_stats[stat_info.id] = {}
            else
                -- Stop iteration entirely if this was the first index but no stat was supplied. We assume that we stop in this case.
                if j == 1 then
                    i = nil
                end
                -- stop iteration
                j = nil
            end
        until j == nil

        -- Don't add this column if no stats were provided.
        if #col_info.stats > 0 then
            stat_columns[#stat_columns+1] = col_info
        end
    until i == nil

    for _, col_info in ipairs(stat_columns) do
        local row_info = {
            --arg
            header = col_info.header,
            fields = {},
            display = function(tr, data, properties)
                if col_info.stat_format == 'separate' then
                    local stat_texts = {}
                    local num_stats = 0
                    local vmax = 0
                    for _, stat_info in ipairs(col_info.stats) do
                        num_stats = num_stats + 1
                        -- stat results from outside body
                        local stat = (results2.stats[data['items._pageName']] or {})[stat_info.id]
                        if stat ~= nil then
                            stat_texts[#stat_texts+1] = m_util.html.format_value(tpl_args, frame, stat, {no_color=true})
                            vmax = vmax + stat.max
                        end
                    end

                    if num_stats ~= #stat_texts then
                        tr:wikitext(m_util.html.td.na())
                    else
                        local text
                        if col_info.format then
                            text = string.format(col_info.format, unpack(stat_texts))
                        else
                            text = table.concat(stat_texts, ', ')
                        end

                        tr:tag('td')
                            :attr('data-sort-value', vmax)
                            :attr('class', 'tc -mod')
                            :wikitext(text)
                    end
                 elseif col_info.stat_format == 'add' then
                    local total_stat = {
                        min = 0,
                        max = 0,
                        avg = 0,
                    }
                    for _, stat_info in ipairs(col_info.stats) do
                        local stat = (results2.stats[data['items._pageName']] or {})[stat_info.id]
                        if stat ~= nil then
                            for k, v in pairs(total_stat) do
                                total_stat[k] = v + stat[k]
                            end
                        end
                    end

                    if col_info.format == nil then
                        col_info.format = '%s'
                    end

                    tr:tag('td')
                        :attr('data-sort-value', total_stat.max)
                        :attr('class', 'tc -mod')
                        :wikitext(string.format(col_info.format, m_util.html.format_value(tpl_args, frame, total_stat, {no_color=true})))
                 else
                    error(string.format(i18n.errors.generic_argument_parameter, 'stat_format', col_info.stat_format))
                 end
            end,
            order = col_info.order,
        }
        table.insert(row_infos, row_info)
    end

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

    -- Parse query arguments
    local tables_assoc = {items=true}
    local fields = {
        'items._pageID',
        'items._pageName',
        'items.name',
        'items.inventory_icon',
        'items.html',
        'items.size_x',
        'items.size_y',
    }

    --
    local prepend = {
        q_groupBy=true,
        q_tables=true,
    }

    local query = {}
    for key, value in pairs(tpl_args) do
        if string.sub(key, 0, 2) == 'q_' then
            if prepend[key] then
                value = ',' .. value
            end

            query[string.sub(key, 3)] = value
        end
    end

    local skill_levels = {}
    for _, rowinfo in ipairs(row_infos) do
        if type(rowinfo.fields) == 'function' then
            rowinfo.fields = rowinfo.fields()
        end
        for index, field in ipairs(rowinfo.fields) do
            rowinfo.options[index] = rowinfo.options[index] or {}
            if rowinfo.options[index].skill_levels then
                skill_levels[#skill_levels+1] = field
            else
                fields[#fields+1] = field
                tables_assoc[m_util.string.split(field, '%.')[1]] = true
            end
        end
    end

    if #skill_levels > 0 then
        fields[#fields+1] = 'skill.max_level'
        tables_assoc.skill = true

    end

    -- Reformat the tables and fields so they can be retrieved correctly:
    local tables = {}
    for table_name,_ in pairs(tables_assoc) do
        tables[#tables+1] = table_name
    end
    local tbls = table.concat(tables,',') .. (query.tables or '')
    query.tables = m_util.string.split(tbls, ',')

    for index, field in ipairs(fields) do
        fields[index] = string.format('%s=%s', field, field)
    end
    query.fields = fields

    -- Take care of the minimum required joins, joins from templates
    -- must still be userdefined:
    local joins = {}
    for index, table_name in ipairs(tables) do
        if table_name ~= 'items' then
            joins[#joins+1] = string.format('items._pageID=%s._pageID', table_name)
        end
    end
    if #joins > 0 and query.join then
        query.join = table.concat(joins, ',') .. ',' .. query.join
    elseif #joins > 0 and not query.join then
        query.join = table.concat(joins, ',')
    elseif #joins == 0 and query.join then
        -- leave query.join as is
    end

    -- Needed to eliminate duplicates supplied via table joins:
    query.groupBy = 'items._pageID' .. (query.groupBy or '')

    -- Query results:
    local results = m_cargo.query(query.tables, query.fields, query)

    if #results == 0 and tpl_args.default ~= nil then
        return tpl_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 pages = {}
            for _, row in ipairs(results) do
                pages[#pages+1] = string.format('item_stats._pageID="%s"', row['items._pageID'])
            end

            local query_stat_ids = {}
            for stat_id, _ in pairs(query_stats) do
                query_stat_ids[#query_stat_ids+1] = string.format('item_stats.id="%s"', stat_id)
            end

            if tpl_args.q_where then
                tpl_args.q_where = string.format(' AND (%s)', tpl_args.q_where)
            else
                tpl_args.q_where = ''
            end

            local temp = m_cargo.query(
                {'items', 'item_stats'},
                {'item_stats._pageName', 'item_stats.id', 'item_stats.min', 'item_stats.max', 'item_stats.avg'},
                {
                    where=string.format('item_stats.is_implicit IS NULL AND (%s) AND (%s)', table.concat(query_stat_ids, ' OR '), table.concat(pages, ' OR ')),
                    join='items._pageID=item_stats._pageID',
                    -- Cargo workaround: avoid duplicates using groupBy
                    groupBy='items._pageID, item_stats.id',
                }
            )

            for _, row in ipairs(temp) do
                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')

    -- Headers:
    local tr = tbl:tag('tr')
    tr
        :tag('th')
            :wikitext(modes[tpl_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 tpl_args.no_html == nil then
            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
            if display then
                rowinfo.display(tr, row, rowinfo.fields, results2)
            else
                tr:wikitext(m_util.html.td.na())
            end
        end
    end

    cats = {}
    if #results == query.limit then
        cats[#cats+1] = i18n.categories.query_limit
    end

    if #results == 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


-------------------------------------------------------------------------------
-- Map item drops
-------------------------------------------------------------------------------

function p.map_item_drops(frame)
    --[[
    Gets the area id from the map item and activates
    Template:Area_item_drops.

    Examples:
    = p.map_item_drops{page='Underground River Map (War for the Atlas)'}
    ]]

    -- Get args
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)

    tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())

    local results = m_cargo.query(
        {'maps'},
        {'maps.area_id'},
        {
            where=string.format('maps._pageName="%s" AND maps.area_id IS NOT NULL', tpl_args.page),
            -- Only need each page name once
            groupBy='maps._pageName',
        }
    )
    local id = ''
    if #results > 0 then
        id = results[1]['maps.area_id']
    end
    return frame:expandTemplate{ title = 'Area item drops', args = {area_id=id} }
end

-------------------------------------------------------------------------------
-- Prophecy description
-------------------------------------------------------------------------------

function p.prophecy_description(frame)
    -- Get args
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)

    tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())

    local results = m_cargo.query(
        {'prophecies'},
        {'prophecies.objective', 'prophecies.reward'},
        {
            where=string.format('prophecies._pageName="%s"', tpl_args.page),
            -- Only need each page name once
            groupBy='prophecies._pageName',
        }
    )

    results = results[1]

    local out = {}

    if results['prophecies.objective'] then
        out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.objective)
        out[#out+1] = results['prophecies.objective']
    end

    if results['prophecies.reward'] then
        out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.reward)
        out[#out+1] = results['prophecies.reward']
    end

    return table.concat(out, '\n')
end

-- ----------------------------------------------------------------------------
-- Item disambiguation
-- ----------------------------------------------------------------------------
function h.find_aliases(tpl_args)
   --[[
   This function queries items for an item name, then checks if it has
   had any name changes then queries for that name as well.
   ]]

    -- Get initial name:
    tpl_args.name_list = {
        tpl_args.name or m_util.string.split(
            tostring(mw.title.getCurrentTitle()),
            ' %('
        )
    }

    -- Query for items with similar name, repeat until no new names are
    -- found.
    local n
    local results = {}
    local hash = {}
    repeat
        local n_old = #tpl_args.name_list

        -- Multiple HOLDS doesn't work. Using __FULL and REGEXP instead.
        local where_tbl = {}
        for _, item_name in ipairs(tpl_args.name_list) do
            for _, prefix in ipairs({'', 'Shaped '}) do
                where_tbl[#where_tbl+1] = string.format(
                    '(items.name_list__FULL REGEXP "(�|^)%s%s(�|$)")',
                    prefix,
                    item_name
                )
            end
        end
        local where_str = table.concat(where_tbl, ' OR ')

        results = m_cargo.query(
            {'items', 'maps'},
            {
                'items._pageName',
                'items.name',
                '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
    until #tpl_args.name_list == n_old

    return results
end

function p.item_disambiguation(frame)
    --[[
    This function finds that items with a name or has had that name.

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


function p.simple_item_list(frame)
    --[[
    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,
    }
    ]]

    -- Args
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)

    local query = {}
    for key, value in pairs(tpl_args) do
        if string.sub(key, 0, 2) == 'q_' then
            query[string.sub(key, 3)] = value
        end
    end

    local fields = {
        'items._pageName',
        'items.name',
        'items.class',
    }

    if tpl_args.no_icon == nil then
        fields[#fields+1] = 'items.inventory_icon'
    end

    if tpl_args.no_html == nil then
        fields[#fields+1] = 'items.html'
    end

    local tables = m_util.string.split(tpl_args.q_tables or '', ',%s*')
    table.insert(tables, 'items')

    query.groupBy = query.groupBy or 'items._pageID'

    local results = m_cargo.query(
        tables,
        fields,
        query
    )

    local out = {}
    for _, row in ipairs(results) do
        local page
        if tpl_args.use_name_as_link ~= nil then
            page = row['items.name']
        else
            page = row['items._pageName']
        end

        local link = f_item_link{
            page=page,
            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
            out[#out+1] = string.format('* %s', link)
        elseif tpl_args.format == 'none' then
            out[#out+1] = link
        elseif tpl_args.format == 'li' then
            out[#out+1] = string.format('<li>%s</li>', link)
        else
            error(string.format(i18n.errors.generic_argument_parameter, 'format', tpl_args.format))
        end
    end

    if tpl_args.format == nil then
        return table.concat(out, '\n')
    elseif tpl_args.format == 'none' then
        return table.concat(out, '\n')
    elseif tpl_args.format == 'li' then
        return table.concat(out)
    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, ', ')
end

function p.debug.generic_item_all()
    return p.debug._tbl_data(data_map.generic_item)
end

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

-- ----------------------------------------------------------------------------
-- Return
-- ----------------------------------------------------------------------------

return p