Module:Item2: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
>OmegaK2
No edit summary
>TheFrz
mNo edit summary
Line 26: Line 26:
-- singular for weapon class in infoboxes
-- singular for weapon class in infoboxes
--
--
-- Maps:  
-- Maps:
--  Area level can be retrieved eventually
--  Area level can be retrieved eventually
--  Hallowed Ground has changed area level from mod
--  Hallowed Ground has changed area level from mod
--
--
-- Essence:  
-- Essence:
--  type column
--  type column
--  monster modifier info
--  monster modifier info
Line 117: Line 117:
     tpl_args._properties[property .. ' HTML'] = html
     tpl_args._properties[property .. ' HTML'] = html
     tpl_args._properties[property .. ' range colour'] = colour
     tpl_args._properties[property .. ' range colour'] = colour
   
 
     fmt_options = mw.clone(fmt_options)
     fmt_options = mw.clone(fmt_options)
     fmt_options.no_color = true
     fmt_options.no_color = true
Line 142: Line 142:


h.stat = {}
h.stat = {}
function h.stat.add (value, stat_cached)  
function h.stat.add (value, stat_cached)
     value.min = value.min + stat_cached.min
     value.min = value.min + stat_cached.min
     value.max = value.max + stat_cached.max
     value.max = value.max + stat_cached.max
Line 198: Line 198:
     return function(tr, data, properties)
     return function(tr, data, properties)
         values = {}
         values = {}
       
 
         for _, prop in ipairs(properties) do
         for _, prop in ipairs(properties) do
             values[#values+1] = data[prop]
             values[#values+1] = data[prop]
         end
         end
       
 
       
 
         local td = tr:tag('td')
         local td = tr:tag('td')
         td:attr('data-sort-value', table.concat(values, ', '))
         td:attr('data-sort-value', table.concat(values, ', '))
Line 251: Line 251:
         end
         end
     end
     end
   
 
     if options.func ~= nil then
     if options.func ~= nil then
         value.min = options.func(tpl_args, frame, value.min)
         value.min = options.func(tpl_args, frame, value.min)
         value.max = options.func(tpl_args, frame, value.max)
         value.max = options.func(tpl_args, frame, value.max)
     end
     end
   
 
     if options.fmt == nil then
     if options.fmt == nil then
         options.fmt = '%s'
         options.fmt = '%s'
Line 262: Line 262:
         options.fmt = options.fmt(tpl_args, frame)
         options.fmt = options.fmt(tpl_args, frame)
     end
     end
   
 
     if value.min == value.max then
     if value.min == value.max then
         value.out = string.format(options.fmt, value.min)
         value.out = string.format(options.fmt, value.min)
Line 268: Line 268:
         value.out = string.format(string.format(options.fmt_range or "(%s to %s)", options.fmt, options.fmt), value.min, value.max)
         value.out = string.format(string.format(options.fmt_range or "(%s to %s)", options.fmt, options.fmt), value.min, value.max)
     end
     end
   
 
     if options.no_color == nil then
     if options.no_color == nil then
         value.out = util.html.poe_color(value.color, value.out)
         value.out = util.html.poe_color(value.color, value.out)
     end
     end
   
 
     local text = {
     local text = {
         before = '',
         before = '',
         after = '',
         after = '',
     }
     }
   
 
     for var, _ in pairs(text) do
     for var, _ in pairs(text) do
         if type(options[var]) == 'string' then
         if type(options[var]) == 'string' then
Line 284: Line 284:
             text[var] = options[var](tpl_args, frame)
             text[var] = options[var](tpl_args, frame)
         end
         end
       
 
         if text[var] ~= '' then
         if text[var] ~= '' then
             local color
             local color
Line 292: Line 292:
                 color = options[var .. '_color']
                 color = options[var .. '_color']
             end
             end
           
 
             if color ~= nil then
             if color ~= nil then
                 text[var] = util.html.poe_color(color, text[var])
                 text[var] = util.html.poe_color(color, text[var])
Line 298: Line 298:
         end
         end
     end
     end
   
 
     local return_color
     local return_color
     if options.return_color ~= nil then
     if options.return_color ~= nil then
Line 323: Line 323:
         type=args.key,
         type=args.key,
     }
     }
   
 
     if value ~= nil then
     if value ~= nil then
         table.insert(tpl_args.mods, value)
         table.insert(tpl_args.mods, value)
Line 344: Line 344:
         end
         end
     end
     end
   
 
     return false
     return false
end
end


function core.process_smw_mods(tpl_args, frame)
function core.process_smw_mods(tpl_args, frame)
     if #tpl_args.mods == 0 then  
     if #tpl_args.mods == 0 then
         return
         return
     end
     end
   
 
     local mods = {}
     local mods = {}
     for _, mod_data in ipairs(tpl_args._mods) do
     for _, mod_data in ipairs(tpl_args._mods) do
Line 359: Line 359:
         end
         end
     end
     end
   
 
     local results = {}
     local results = {}
     local result
     local result
Line 365: Line 365:
     local number_of_queries = math.ceil(#tpl_args.mods / c.max_mod_params)
     local number_of_queries = math.ceil(#tpl_args.mods / c.max_mod_params)
     local pages = {}
     local pages = {}
   
 
     for query_number=1, number_of_queries do
     for query_number=1, number_of_queries do
         local offset = (query_number-1)*c.max_mod_params
         local offset = (query_number-1)*c.max_mod_params
Line 372: Line 372:
             mod_ids[#mod_ids+1] = tpl_args.mods[j]
             mod_ids[#mod_ids+1] = tpl_args.mods[j]
         end
         end
   
 
         query = {
         query = {
             string.format('[[Is mod::%s]]', table.concat(mod_ids, '||')),
             string.format('[[Is mod::%s]]', table.concat(mod_ids, '||')),
Line 382: Line 382:
             '?Has level requirement#',
             '?Has level requirement#',
         }
         }
       
 
         result = util.smw.query(query, frame)
         result = util.smw.query(query, frame)
   
 
         -- remap this as table with modids as key
         -- remap this as table with modids as key
         for _, row in ipairs(result) do
         for _, row in ipairs(result) do
             results[#results+1] = row
             results[#results+1] = row
           
 
             local mod_data = mods[row['Is mod']]
             local mod_data = mods[row['Is mod']]
             mod_data.result = row
             mod_data.result = row
           
 
             -- needed for the query
             -- needed for the query
             pages[#pages+1] = row[1]
             pages[#pages+1] = row[1]
             -- needed for the mapping stats to mods
             -- needed for the mapping stats to mods
             pages[row[1]] = mod_data
             pages[row[1]] = mod_data
           
 
             -- update item level requirement
             -- update item level requirement
             local keys = {'required_level_final'}
             local keys = {'required_level_final'}
Line 403: Line 403:
                 keys[#keys+1] = 'required_level'
                 keys[#keys+1] = 'required_level'
             end
             end
           
 
             for _, key in ipairs(keys) do
             for _, key in ipairs(keys) do
                 local req = math.floor(tonumber(row['Has level requirement']) * 0.8)
                 local req = math.floor(tonumber(row['Has level requirement']) * 0.8)
Line 412: Line 412:
         end
         end
     end
     end
   
 
     -- TODO: Can items have mods twice? I dont think so, if they do it would need to be accounted for here
     -- TODO: Can items have mods twice? I dont think so, if they do it would need to be accounted for here
     if #results ~= #tpl_args.mods then
     if #results ~= #tpl_args.mods then
Line 430: Line 430:
             end
             end
         end
         end
       
 
         local text = {}
         local text = {}
         for modid, pages in pairs(duplicates) do
         for modid, pages in pairs(duplicates) do
Line 439: Line 439:
             end
             end
         end
         end
       
 
         error(string.format('Number of mods found does not match number of queried mods. Not found: "%s", duplicates: "%s"', table.concat(missing, ', '), table.concat(text)))
         error(string.format('Number of mods found does not match number of queried mods. Not found: "%s", duplicates: "%s"', table.concat(missing, ', '), table.concat(text)))
     end
     end
   
 
     -- fetch stats
     -- fetch stats
   
 
     number_of_queries = math.ceil(#pages / c.max_stat_params)
     number_of_queries = math.ceil(#pages / c.max_stat_params)
   
 
     for query_number=1, number_of_queries do
     for query_number=1, number_of_queries do
         local offset = (query_number-1)*c.max_stat_params
         local offset = (query_number-1)*c.max_stat_params
Line 453: Line 453:
             temp_pages[#temp_pages+1] = pages[j]
             temp_pages[#temp_pages+1] = pages[j]
         end
         end
       
 
         query = {
         query = {
             string.format('[[-Has subobject::%s]] [[Has stat id::+]] [[Has minimum stat value::+]] [[Has maximum stat value::+]]', table.concat(temp_pages, '||')),
             string.format('[[-Has subobject::%s]] [[Has stat id::+]] [[Has minimum stat value::+]] [[Has maximum stat value::+]]', table.concat(temp_pages, '||')),
Line 461: Line 461:
             '?Has maximum stat value#',
             '?Has maximum stat value#',
         }
         }
       
 
         local stats = util.smw.query(query, frame)
         local stats = util.smw.query(query, frame)
       
 
         -- process and cache stats
         -- process and cache stats
         for _, stat in ipairs(stats) do
         for _, stat in ipairs(stats) do
Line 472: Line 472:
                 mod_data.result.stats[#mod_data.result.stats+1] = stat
                 mod_data.result.stats[#mod_data.result.stats+1] = stat
             end
             end
       
 
             local id = stat['Has stat id']
             local id = stat['Has stat id']
             local value = {
             local value = {
Line 479: Line 479:
             }
             }
             value.avg = (value.min+value.max)/2
             value.avg = (value.min+value.max)/2
           
 
             h.stats_update(tpl_args, id, value, mod_data.result['Is mod'], '_stats')
             h.stats_update(tpl_args, id, value, mod_data.result['Is mod'], '_stats')
             if mod_data.type ~= 'implicit' then
             if mod_data.type ~= 'implicit' then
Line 502: Line 502:
         return
         return
     end
     end
   
 
     if #query > 1 and tpl_args.rarity == 'Normal' then
     if #query > 1 and tpl_args.rarity == 'Normal' then
         error(core.err{msg='Base item parameter is set, but rarity is set to normal. A rarity above normal is required!'})
         error(core.err{msg='Base item parameter is set, but rarity is set to normal. A rarity above normal is required!'})
     end
     end
   
 
     query[#query] = query[#query] .. string.format('[[Has item class::%s]] [[Has rarity::Normal]]', tpl_args['class'])
     query[#query] = query[#query] .. string.format('[[Has item class::%s]] [[Has rarity::Normal]]', tpl_args['class'])
     query[#query+1] = '?Has implicit mod ids#'
     query[#query+1] = '?Has implicit mod ids#'
     query[#query+1] = '?Has metadata id'
     query[#query+1] = '?Has metadata id'
     query[#query+1] = '?Has name'
     query[#query+1] = '?Has name'
   
 
     for _, k in ipairs(tpl_args._base_item_args) do
     for _, k in ipairs(tpl_args._base_item_args) do
         if core.map[k].property ~= nil then
         if core.map[k].property ~= nil then
Line 517: Line 517:
         end
         end
     end
     end
   
 
     local result = util.smw.query(query, frame)
     local result = util.smw.query(query, frame)
   
 
     if #result > 1 then
     if #result > 1 then
         error(core.err{msg='More then one result found for the specified base item. Consider using base_item_page or base_item_id to narrow down the results.'})
         error(core.err{msg='More then one result found for the specified base item. Consider using base_item_page or base_item_id to narrow down the results.'})
         -- TODO be more explicit in the error?
         -- TODO be more explicit in the error?
     end
     end
   
 
     result = result[1]
     result = result[1]
   
 
     tpl_args.base_item_data = result
     tpl_args.base_item_data = result
     core.process_arguments(tpl_args, frame, {array={'base_item', 'base_item_page', 'base_item_id'}})
     core.process_arguments(tpl_args, frame, {array={'base_item', 'base_item_page', 'base_item_id'}})
Line 580: Line 580:
     end
     end
end
end
       
 
function core.process_mod_stats(tpl_args, args)
function core.process_mod_stats(tpl_args, args)
     local lines = {}
     local lines = {}
   
 
     local skip = core.class_specifics[tpl_args.class]
     local skip = core.class_specifics[tpl_args.class]
     if skip then
     if skip then
         skip = skip.skip_stat_lines
         skip = skip.skip_stat_lines
     end  
     end
   
 
     for _, modinfo in ipairs(tpl_args._mods) do
     for _, modinfo in ipairs(tpl_args._mods) do
         if modinfo.type == args.type then
         if modinfo.type == args.type then
Line 615: Line 615:
         end
         end
     end
     end
   
 
     if #lines == 0 then
     if #lines == 0 then
         return
         return
Line 629: Line 629:
         :wikitext(args.msg or ('Argument ' .. args.key .. ' to item template is invalid. Please check the documention for acceptable values.'))
         :wikitext(args.msg or ('Argument ' .. args.key .. ' to item template is invalid. Please check the documention for acceptable values.'))
         :done()
         :done()
       
 
     return tostring(err)
     return tostring(err)
end
end
Line 644: Line 644:
     return function (tpl_args, frame)
     return function (tpl_args, frame)
         local elements
         local elements
       
 
         if tpl_args[k] ~= nil then
         if tpl_args[k] ~= nil then
             elements = util.string.split(tpl_args[k], ', ')
             elements = util.string.split(tpl_args[k], ', ')
             for _, element in ipairs(elements) do  
             for _, element in ipairs(elements) do
                 local r = util.table.find_in_nested_array{value=element, tbl=args.tbl, key='full'}
                 local r = util.table.find_in_nested_array{value=element, tbl=args.tbl, key='full'}
                 if r == nil then
                 if r == nil then
Line 686: Line 686:
     return function (tpl_args, frame)
     return function (tpl_args, frame)
         local v = tonumber(tpl_args[k])
         local v = tonumber(tpl_args[k])
       
 
         if v == nil then
         if v == nil then
             return core.err{key=k}
             return core.err{key=k}
         end
         end
       
 
         if v < 0 or v > 100 then
         if v < 0 or v > 100 then
             return core.err{msg=k .. ' must be in range 0-100.'}
             return core.err{msg=k .. ' must be in range 0-100.'}
         end
         end
       
 
         tpl_args[k] = v
         tpl_args[k] = v
     end
     end
Line 717: Line 717:
     --  color: colour code for util.html.poe_color, overrides mod colour
     --  color: colour code for util.html.poe_color, overrides mod colour
     --  no_color: set to true to ingore colour entirely
     --  no_color: set to true to ingore colour entirely
   
 
     for k, default in pairs({options = {}}) do
     for k, default in pairs({options = {}}) do
         if args[k] == nil then
         if args[k] == nil then
Line 723: Line 723:
         end
         end
     end
     end
   
 
     return function (tpl_args, frame)
     return function (tpl_args, frame)
         local base_values = {}
         local base_values = {}
Line 739: Line 739:
                     value = {
                     value = {
                         min=tpl_args[string.format('level1_%s', data.key)],
                         min=tpl_args[string.format('level1_%s', data.key)],
                         max=tpl_args[string.format('level%s_%s', tpl_args.max_level, data.key)],  
                         max=tpl_args[string.format('level%s_%s', tpl_args.max_level, data.key)],
                     }
                     }
                     if value.min == nil or value.max == nil then
                     if value.min == nil or value.max == nil then
Line 773: Line 773:
             end
             end
         end
         end
       
 
         local final_values = {}
         local final_values = {}
         for i, data in ipairs(temp_values) do
         for i, data in ipairs(temp_values) do
Line 798: Line 798:
                 end
                 end
             end
             end
           
 
             if insert == true then
             if insert == true then
                 table.insert(final_values, data)
                 table.insert(final_values, data)
             end
             end
         end
         end
       
 
         -- all zeros = dont display and return early
         -- all zeros = dont display and return early
         if #final_values == 0 then
         if #final_values == 0 then
             return nil
             return nil
         end
         end
       
 
         local out = {}
         local out = {}
       
 
         if args.before then
         if args.before then
             out[#out+1] = util.html.poe_color('default', args.before)
             out[#out+1] = util.html.poe_color('default', args.before)
         end
         end
       
 
         for i, data in ipairs(final_values) do
         for i, data in ipairs(final_values) do
             local value = data.value
             local value = data.value
             value.base = base_values[data.index]
             value.base = base_values[data.index]
           
 
             local options = args.options[data.index]
             local options = args.options[data.index]
           
 
             if options.color == nil and args.type == 'gem' then
             if options.color == nil and args.type == 'gem' then
                 value.color = 'value'
                 value.color = 'value'
             end
             end
           
 
             out[#out+1] = h.format_value(tpl_args, frame, value, options)
             out[#out+1] = h.format_value(tpl_args, frame, value, options)
         end
         end
       
 
         if args.after then
         if args.after then
             out[#out+1] = util.html.poe_color('default', args.after)
             out[#out+1] = util.html.poe_color('default', args.after)
         end
         end
       
 
         return table.concat(out, '')
         return table.concat(out, '')
     end
     end
Line 867: Line 867:
         }
         }
         local value = {}
         local value = {}
       
 
         for ktype, key in pairs(keys) do
         for ktype, key in pairs(keys) do
             value[ktype] = core.factory.display_value{options={ [1] = {
             value[ktype] = core.factory.display_value{options={ [1] = {
Line 875: Line 875:
             }}}(tpl_args, frame)
             }}}(tpl_args, frame)
         end
         end
       
 
         if value.min and value.max then
         if value.min and value.max then
             value = value.min .. '&ndash;' .. value.max
             value = value.min .. '&ndash;' .. value.max
Line 902: Line 902:
-- format:
-- format:
-- tpl_args key = {
-- tpl_args key = {
--  no_copy = true or nil          -- When loading an base item, dont copy this key  
--  no_copy = true or nil          -- When loading an base item, dont copy this key
--  property = 'prop',              -- Property associated with this key
--  property = 'prop',              -- Property associated with this key
--  property_func = function or nil -- Function to unpack the property into a native lua value.  
--  property_func = function or nil -- Function to unpack the property into a native lua value.
--                                      If not specified, func is used.  
--                                      If not specified, func is used.
--                                      If neither is specified, value is copied as string
--                                      If neither is specified, value is copied as string
--  func = function or nil          -- Function to unpack the argument into a native lua value and validate it.  
--  func = function or nil          -- Function to unpack the argument into a native lua value and validate it.
--                                      If not specified, value will not be set.
--                                      If not specified, value will not be set.
--  default = object                -- Default value if the parameter is nil
--  default = object                -- Default value if the parameter is nil
Line 928: Line 928:
         func = function(tpl_args, frame)
         func = function(tpl_args, frame)
             tpl_args.explicit_stat_text = core.process_mod_stats(tpl_args, {type='explicit'})
             tpl_args.explicit_stat_text = core.process_mod_stats(tpl_args, {type='explicit'})
           
 
             if tpl_args.is_talisman or tpl_args.is_corrupted then
             if tpl_args.is_talisman or tpl_args.is_corrupted then
                 if tpl_args.explicit_stat_text == nil or tpl_args.explicit_stat_text == '' then
                 if tpl_args.explicit_stat_text == nil or tpl_args.explicit_stat_text == '' then
Line 946: Line 946:
             end
             end
             local text = (tpl_args.implicit_stat_text or '') .. sep .. (tpl_args.explicit_stat_text or '')
             local text = (tpl_args.implicit_stat_text or '') .. sep .. (tpl_args.explicit_stat_text or '')
           
 
             if string.len(text) > 0 then
             if string.len(text) > 0 then
                 tpl_args.stat_text = text
                 tpl_args.stat_text = text
Line 1,023: Line 1,023:
         func = function(tpl_args, frame)
         func = function(tpl_args, frame)
             tpl_args.inventory_icon_id = tpl_args.inventory_icon or tpl_args.name
             tpl_args.inventory_icon_id = tpl_args.inventory_icon or tpl_args.name
             tpl_args.inventory_icon = string.format('File:%s inventory icon.png', tpl_args.inventory_icon_id)  
             tpl_args.inventory_icon = string.format('File:%s inventory icon.png', tpl_args.inventory_icon_id)
         end,
         end,
     },
     },
Line 1,034: Line 1,034:
             if tpl_args.alternate_art_inventory_icons ~= nil then
             if tpl_args.alternate_art_inventory_icons ~= nil then
                 local names = util.string.split(tpl_args.alternate_art_inventory_icons, ', ')
                 local names = util.string.split(tpl_args.alternate_art_inventory_icons, ', ')
               
 
                 for _, name in ipairs(names) do
                 for _, name in ipairs(names) do
                     icons[#icons+1] = string.format('File:%s %s inventory icon.png', tpl_args.inventory_icon_id, name)  
                     icons[#icons+1] = string.format('File:%s %s inventory icon.png', tpl_args.inventory_icon_id, name)
                 end
                 end
             end
             end
Line 1,046: Line 1,046:
         property = 'Has buff icon',
         property = 'Has buff icon',
         func = function(tpl_args, frame)
         func = function(tpl_args, frame)
             tpl_args.buff_icon = string.format('File:%s status icon.png', tpl_args.name)  
             tpl_args.buff_icon = string.format('File:%s status icon.png', tpl_args.name)
         end,
         end,
     },
     },
Line 1,076: Line 1,076:
         no_copy = true,
         no_copy = true,
         property = 'Is corrupted',
         property = 'Is corrupted',
         func = core.factory.boolean_cast('is_corrupted'),  
         func = core.factory.boolean_cast('is_corrupted'),
         default = false,
         default = false,
     },
     },
Line 1,087: Line 1,087:
         func = core.factory.boolean_cast('is_talisman'),
         func = core.factory.boolean_cast('is_talisman'),
     },
     },
   
 
     -- flasks
     -- flasks
     charges_max = {
     charges_max = {
Line 1,135: Line 1,135:
                 end
                 end
             end
             end
           
 
             tpl_args.buff_values = values
             tpl_args.buff_values = values
         end,
         end,
Line 1,143: Line 1,143:
             local value
             local value
             local key
             local key
             repeat  
             repeat
                 i = i + 1
                 i = i + 1
                 key = 'buff_value' .. i
                 key = 'buff_value' .. i
Line 1,154: Line 1,154:
                 }
                 }
             until values[i] == nil
             until values[i] == nil
           
 
             tpl_args.buff_values = values
             tpl_args.buff_values = values
         end,
         end,
Line 1,162: Line 1,162:
         func = nil,
         func = nil,
     },
     },
   
 
     -- weapons & active skills
     -- weapons & active skills
     critical_strike_chance = {
     critical_strike_chance = {
Line 1,227: Line 1,227:
         func = function(tpl_args, frame)
         func = function(tpl_args, frame)
             for _, attr in ipairs(m_game.constants.attributes) do
             for _, attr in ipairs(m_game.constants.attributes) do
                 local val = tpl_args[attr.long_lower .. '_percent']  
                 local val = tpl_args[attr.long_lower .. '_percent']
                 if val and val >= 60 then
                 if val and val >= 60 then
                     tpl_args['primary_attribute'] = attr.long_upper
                     tpl_args['primary_attribute'] = attr.long_upper
Line 1,260: Line 1,260:
         func = function(tpl_args, frame)
         func = function(tpl_args, frame)
             -- TODO readd support if needed.
             -- TODO readd support if needed.
             tpl_args.gem_icon = string.format('File:%s skill icon.png', tpl_args.name)  
             tpl_args.gem_icon = string.format('File:%s skill icon.png', tpl_args.name)
         end,
         end,
     },
     },
Line 1,270: Line 1,270:
                 return
                 return
             end
             end
       
 
             -- TODO replace this with a loop possibly
             -- TODO replace this with a loop possibly
             local css_map = {
             local css_map = {
Line 1,285: Line 1,285:
                 end
                 end
             end
             end
           
 
             if id ~= nil then
             if id ~= nil then
                 local container = mw.html.create('span')
                 local container = mw.html.create('span')
Line 1,432: Line 1,432:
     -- derived stats
     -- derived stats
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
   
 
     -- For rarity != normal, rarity already verified
     -- For rarity != normal, rarity already verified
     base_item = {
     base_item = {
Line 1,455: Line 1,455:
         end,
         end,
     },
     },
   
 
     name_list = {
     name_list = {
         no_copy = true,
         no_copy = true,
Line 1,494: Line 1,494:
                     gtags[tag] = nil
                     gtags[tag] = nil
                 end
                 end
               
 
                 -- add them as ordered list and not as hash table so it is consistent with the other gem tag list
                 -- add them as ordered list and not as hash table so it is consistent with the other gem tag list
                 tpl_args.gem_tags_difference = {}
                 tpl_args.gem_tags_difference = {}
Line 1,511: Line 1,511:
                 return
                 return
             end
             end
       
 
             local var = core.class_specifics[tpl_args.class]
             local var = core.class_specifics[tpl_args.class]
             if var ~= nil and var.frame_type ~= nil then
             if var ~= nil and var.frame_type ~= nil then
Line 1,517: Line 1,517:
                 return
                 return
             end
             end
           
 
             tpl_args.frame_type = string.lower(tpl_args.rarity)
             tpl_args.frame_type = string.lower(tpl_args.rarity)
         end,
         end,
Line 1,523: Line 1,523:
     --
     --
     -- args populated by mod validation
     -- args populated by mod validation
     --  
     --
     mods = {
     mods = {
         no_copy = true,
         no_copy = true,
Line 1,533: Line 1,533:
         property_func = function (tpl_args)
         property_func = function (tpl_args)
             tpl_args.implicit_mods = util.string.split(tpl_args.implicit_mods, '<MANY>')
             tpl_args.implicit_mods = util.string.split(tpl_args.implicit_mods, '<MANY>')
             for _, modid in ipairs(tpl_args.implicit_mods) do  
             for _, modid in ipairs(tpl_args.implicit_mods) do
                 tpl_args.mods[#tpl_args.mods+1] = modid
                 tpl_args.mods[#tpl_args.mods+1] = modid
                 tpl_args._mods[#tpl_args._mods+1] = {
                 tpl_args._mods[#tpl_args._mods+1] = {
Line 1,584: Line 1,584:
                 end
                 end
             end
             end
           
 
             dmg = (dmg.min + dmg.max) / 2
             dmg = (dmg.min + dmg.max) / 2
           
 
             tpl_args.damage_avg = dmg
             tpl_args.damage_avg = dmg
         end,
         end,
Line 2,045: Line 2,045:
-- base item is default, but will be validated later
-- base item is default, but will be validated later
-- Notes:
-- Notes:
--  inventory_icon must always be before alternate_art_inventory_icons  
--  inventory_icon must always be before alternate_art_inventory_icons
core.default_args = {'class', 'rarity', 'name', 'name_list', 'name_list_lower', 'size_x', 'size_y', 'drop_enabled', 'drop_level', 'drop_level_maximum', 'required_level', 'required_level_final', 'inventory_icon', 'alternate_art_inventory_icons', 'flavour_text', 'help_text', 'tags', 'metadata_id', 'is_corrupted', 'mods', 'implicit_mods', 'explicit_mods'}
core.default_args = {'class', 'rarity', 'name', 'name_list', 'name_list_lower', 'size_x', 'size_y', 'drop_enabled', 'drop_level', 'drop_level_maximum', 'required_level', 'required_level_final', 'inventory_icon', 'alternate_art_inventory_icons', 'flavour_text', 'help_text', 'tags', 'metadata_id', 'is_corrupted', 'mods', 'implicit_mods', 'explicit_mods'}
-- frame_type is needed in stat_text
-- frame_type is needed in stat_text
Line 2,205: Line 2,205:
end
end


-- GroupTable -> RowTable -> formatter function  
-- GroupTable -> RowTable -> formatter function
--
--
--
--
Line 2,226: Line 2,226:
         {
         {
             args = function(tpl_args, frame)
             args = function(tpl_args, frame)
                 if tpl_args.class == nil then  
                 if tpl_args.class == nil then
                     return false  
                     return false
                 end
                 end
               
 
                 return core.class_groups.weapons.keys[tpl_args.class] ~= nil
                 return core.class_groups.weapons.keys[tpl_args.class] ~= nil
             end,
             end,
Line 2,248: Line 2,248:
                     out[#out+1] = string.format('[[:Category:%s (gem tag)|%s]]', tag, tag)
                     out[#out+1] = string.format('[[:Category:%s (gem tag)|%s]]', tag, tag)
                 end
                 end
               
 
                 return table.concat(out, ', ')
                 return table.concat(out, ', ')
             end,
             end,
Line 2,392: Line 2,392:
                 options = {
                 options = {
                     [1] = {
                     [1] = {
                         key = 'cooldown',  
                         key = 'cooldown',
                         hide_default = 0,
                         hide_default = 0,
                         fmt = '%.2f sec',
                         fmt = '%.2f sec',
Line 2,463: Line 2,463:
                     },
                     },
                 },
                 },
             },      
             },
         },
         },
         {
         {
Line 2,475: Line 2,475:
                     end
                     end
                 end
                 end
               
 
                 if text ~= '' then
                 if text ~= '' then
                     return 'Elemental Damage:' .. text
                     return 'Elemental Damage:' .. text
Line 2,481: Line 2,481:
                     return
                     return
                 end
                 end
             end,    
             end,
         },
         },
         {
         {
Line 2,493: Line 2,493:
                     },
                     },
                 },
                 },
             },      
             },
         },
         },
         {
         {
Line 2,665: Line 2,665:
                         fmt = '%s',
                         fmt = '%s',
                         func = function(value)
                         func = function(value)
                             return string.format('%s (%i)', (m_game.constants.item.jewel_radius_to_size[value] or '?'), value)  
                             return string.format('%s (%i)', (m_game.constants.item.jewel_radius_to_size[value] or '?'), value)
                         end,
                         end,
                         before = 'Radius: ',
                         before = 'Radius: ',
Line 2,718: Line 2,718:
                     end
                     end
                 end
                 end
               
 
                 return tpl_args['flask_duration_html'] ~= nil
                 return tpl_args['flask_duration_html'] ~= nil
             end,
             end,
Line 2,859: Line 2,859:
                     end
                     end
                 end
                 end
               
 
                 return util.html.poe_color('default', 'Requires ') .. string.format('[[%s|%s %s]]', data.full, data.short_upper, tpl_args.master_level_requirement)
                 return util.html.poe_color('default', 'Requires ') .. string.format('[[%s|%s %s]]', data.full, data.short_upper, tpl_args.master_level_requirement)
             end
             end
Line 2,909: Line 2,909:
                     },
                     },
                 }
                 }
               
 
                 for _, attr in ipairs(m_game.constants.attributes) do
                 for _, attr in ipairs(m_game.constants.attributes) do
                     opt[#opt+1] = {
                     opt[#opt+1] = {
Line 2,921: Line 2,921:
                     }
                     }
                 end
                 end
               
 
                 local requirements = core.factory.display_value{options = opt}(tpl_args, frame)
                 local requirements = core.factory.display_value{options = opt}(tpl_args, frame)
               
 
                 -- return early
                 -- return early
                 if requirements == nil then
                 if requirements == nil then
                     return
                     return
                 end
                 end
               
 
                 requirements = string.gsub(requirements, '^, ', '')
                 requirements = string.gsub(requirements, '^, ', '')
               
 
                 return util.html.poe_color('default', 'Requires ') .. requirements
                 return util.html.poe_color('default', 'Requires ') .. requirements
             end,
             end,
Line 2,952: Line 2,952:
                 lines[#lines+1] = util.html.poe_color('default', 'Per 1% Quality:')
                 lines[#lines+1] = util.html.poe_color('default', 'Per 1% Quality:')
                 lines[#lines+1] = tpl_args.quality_stat_text
                 lines[#lines+1] = tpl_args.quality_stat_text
               
 
                 return table.concat(lines, '<br>')
                 return table.concat(lines, '<br>')
             end,
             end,
Line 2,968: Line 2,968:
                 lines[#lines+1] = tpl_args.stat_text
                 lines[#lines+1] = tpl_args.stat_text
                 if tpl_args.gem_tags:contains('Vaal') then
                 if tpl_args.gem_tags:contains('Vaal') then
                     lines[#lines+1] = util.html.poe_color('corrupted', 'Corrupted')  
                     lines[#lines+1] = util.html.poe_color('corrupted', 'Corrupted')
                 end
                 end
                 return table.concat(lines, '<br>')
                 return table.concat(lines, '<br>')
Line 3,092: Line 3,092:
                         fmt = '%dx',
                         fmt = '%dx',
                         color = 'currency',
                         color = 'currency',
                         after = function (tpl_args, frame)  
                         after = function (tpl_args, frame)
                             -- direct call will mess up tpl_args
                             -- direct call will mess up tpl_args
                             return p.item_link{item_name_exact='Silver Coin'}
                             return p.item_link{item_name_exact='Silver Coin'}
Line 3,200: Line 3,200:
         display = h.tbl.display.factory.range{property='maximum physical damage'},
         display = h.tbl.display.factory.range{property='maximum physical damage'},
         order = 7001,
         order = 7001,
       
 
     },
     },
     {
     {
Line 3,357: Line 3,357:
         properties = h.tbl.range_properties(data.property),
         properties = h.tbl.range_properties(data.property),
         display = h.tbl.display.factory.range{property=data.property},
         display = h.tbl.display.factory.range{property=data.property},
         order = 8100+i,  
         order = 8100+i,
     })
     })
end
end
Line 3,440: Line 3,440:
                 appendix = appendix .. ' ' .. util.html.abbr('R', 'reserves mana')
                 appendix = appendix .. ' ' .. util.html.abbr('R', 'reserves mana')
             end
             end
           
 
             local str
             local str
           
 
             if value ~= nil then
             if value ~= nil then
                 str = string.format('%d', value) .. appendix
                 str = string.format('%d', value) .. appendix
             end  
             end
             return h.na_or_val(tr, str)
             return h.na_or_val(tr, str)
         end,
         end,
Line 3,455: Line 3,455:
         properties = {'Has vaal souls requirement'},
         properties = {'Has vaal souls requirement'},
         cast = tonumber,
         cast = tonumber,
         display = function (tr, value, data)  
         display = function (tr, value, data)
             return h.na_or_val(tr, value, function (value)
             return h.na_or_val(tr, value, function (value)
                 return string.format('%d / %d / %d', value, value*1.5, value*2)
                 return string.format('%d / %d / %d', value, value*1.5, value*2)
Line 3,476: Line 3,476:
         cast = tonumber,
         cast = tonumber,
         display = function (tr, value, data)
         display = function (tr, value, data)
             return h.na_or_val(tr, value, function (value)  
             return h.na_or_val(tr, value, function (value)
                 return core.factory.descriptor_value{key='?Has primary radius description', tbl=data}(nil, nil, value)  
                 return core.factory.descriptor_value{key='?Has primary radius description', tbl=data}(nil, nil, value)
             end)
             end)
         end,
         end,
Line 3,488: Line 3,488:
         cast = tonumber,
         cast = tonumber,
         display = function (tr, value, data)
         display = function (tr, value, data)
             return h.na_or_val(tr, value, function (value)  
             return h.na_or_val(tr, value, function (value)
                 return core.factory.descriptor_value{key='?Has secondary radius description', tbl=data}(nil, nil, value)  
                 return core.factory.descriptor_value{key='?Has secondary radius description', tbl=data}(nil, nil, value)
             end)
             end)
         end,
         end,
Line 3,500: Line 3,500:
         cast = tonumber,
         cast = tonumber,
         display = function (tr, value, data)
         display = function (tr, value, data)
             return h.na_or_val(tr, value, function (value)  
             return h.na_or_val(tr, value, function (value)
                 return core.factory.descriptor_value{key='?Has tertiary radius description', tbl=data}(nil, nil, value)  
                 return core.factory.descriptor_value{key='?Has tertiary radius description', tbl=data}(nil, nil, value)
             end)
             end)
         end,
         end,
Line 3,520: Line 3,520:
         property = string.format('Has %s percentage', attr.long_lower),
         property = string.format('Has %s percentage', attr.long_lower),
         cast = tonumber,
         cast = tonumber,
         display = function (tr, value)  
         display = function (tr, value)
             return h.na_or_val(tr, value, function (value)
             return h.na_or_val(tr, value, function (value)
                 return '[[File:Yes.png|yes|link=]]'
                 return '[[File:Yes.png|yes|link=]]'
Line 3,541: Line 3,541:
     --
     --
     local t = os.clock()
     local t = os.clock()
   
 
     local tpl_args = getArgs(frame, {
     local tpl_args = getArgs(frame, {
         parentFirst = true
         parentFirst = true
     })
     })
     frame = util.misc.get_frame(frame)
     frame = util.misc.get_frame(frame)
   
 
     --
     --
     -- Shared args
     -- Shared args
     --
     --
   
 
     core.build_item_classes()
     core.build_item_classes()
   
 
     tpl_args._flags = {}
     tpl_args._flags = {}
     tpl_args._total_args = {}
     tpl_args._total_args = {}
Line 3,561: Line 3,561:
     tpl_args._subobjects = {}
     tpl_args._subobjects = {}
     tpl_args._properties = {}
     tpl_args._properties = {}
   
 
     -- Using general purpose function to handle release and removal versions
     -- Using general purpose function to handle release and removal versions
     util.args.version(tpl_args, {frame=frame, set_properties=true})
     util.args.version(tpl_args, {frame=frame, set_properties=true})
   
 
     -- Must validate some argument early. It is required for future things
     -- Must validate some argument early. It is required for future things
     core.process_arguments(tpl_args, frame, {array=core.default_args})
     core.process_arguments(tpl_args, frame, {array=core.default_args})
     core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].args})
     core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].args})
   
 
     -- set defaults
     -- set defaults
   
 
     for k, v in pairs(core.item_classes[tpl_args.class].defaults) do
     for k, v in pairs(core.item_classes[tpl_args.class].defaults) do
         if tpl_args[k] == nil then
         if tpl_args[k] == nil then
Line 3,576: Line 3,576:
         end
         end
     end
     end
   
 
     -- Base Item
     -- Base Item


     core.process_base_item(tpl_args, frame)
     core.process_base_item(tpl_args, frame)
   
 
     -- Prophecy special snowflake
     -- Prophecy special snowflake
     if tpl_args.base_item == 'Prophecy' then
     if tpl_args.base_item == 'Prophecy' then
Line 3,587: Line 3,587:
             return err
             return err
         end
         end
       
 
         tpl_args.inventory_icon = 'File:Prophecy inventory icon.png'
         tpl_args.inventory_icon = 'File:Prophecy inventory icon.png'
     end
     end
   
 
     -- Mods
     -- Mods


Line 3,601: Line 3,601:
         end
         end
     end
     end
   
 
     core.process_smw_mods(tpl_args, frame)
     core.process_smw_mods(tpl_args, frame)
       
 
     -- Add stats - this is for when mods are not set, but we still need stats to calcuate new armour values etc
     -- Add stats - this is for when mods are not set, but we still need stats to calcuate new armour values etc
     util.args.stats(tpl_args, {prefix='extra_'})
     util.args.stats(tpl_args, {prefix='extra_'})
Line 3,633: Line 3,633:
         }
         }
     end
     end
   
 
     -- Handle extra stats (for gems)
     -- Handle extra stats (for gems)
   
 
     if core.class_groups.gems.keys[tpl_args.class] then
     if core.class_groups.gems.keys[tpl_args.class] then
         m_skill.skill(frame, tpl_args)
         m_skill.skill(frame, tpl_args)
     end
     end
   
 
     --
     --
     -- Handle local stats increases/reductions/additions
     -- Handle local stats increases/reductions/additions
     --
     --
   
 
     local skip = {}
     local skip = {}
     -- override attributes for tabula rasa
     -- override attributes for tabula rasa
Line 3,649: Line 3,649:
         tpl_args.required_level_final = 1
         tpl_args.required_level_final = 1
     end
     end
   
 
     -- general stats
     -- general stats
     for k, data in pairs(core.stat_map) do
     for k, data in pairs(core.stat_map) do
         local value = tpl_args[k]
         local value = tpl_args[k]
 
         if value == nil and data.default ~= nil then
         if value == nil and data.default ~= nil then
             value = data.default
             value = data.default
             tpl_args[k] = data.default
             tpl_args[k] = data.default
         end
         end
       
 
         if value ~= nil and skip[k] == nil then
         if value ~= nil and skip[k] == nil then
             value = {min=value, max=value, base=value}
             value = {min=value, max=value, base=value}
Line 3,680: Line 3,680:
                 end
                 end
           end
           end
               
 
           if overridden == false then
           if overridden == false then
                 -- The simple cases; this must be using ipairs as "add" must apply before
                 -- The simple cases; this must be using ipairs as "add" must apply before
Line 3,693: Line 3,693:
                     end
                     end
                 end
                 end
               
 
                 -- For increased stats we need to add them up first
                 -- For increased stats we need to add them up first
                 for stat_key, stat_func in pairs({increased=h.stat.more, increased_inverse=h.stat.more_inverse}) do
                 for stat_key, stat_func in pairs({increased=h.stat.more, increased_inverse=h.stat.more_inverse}) do
Line 3,709: Line 3,709:
                     end
                     end
                 end
                 end
               
 
                 if data.minimum ~= nil then
                 if data.minimum ~= nil then
                     for _, key in ipairs({'min', 'max'}) do
                     for _, key in ipairs({'min', 'max'}) do
                         if value[key] < data.minimum then
                         if value[key] < data.minimum then
                             value[key] = data.minimum  
                             value[key] = data.minimum
                         end
                         end
                     end
                     end
Line 3,720: Line 3,720:


             end
             end
           
 
             value.avg = (value.min + value.max) / 2
             value.avg = (value.min + value.max) / 2
           
 
             -- don't add the properties unless we need to
             -- don't add the properties unless we need to
             if (data.default ~= nil and (value.min ~= data.default or value.max ~= data.default)) or data.default == nil then
             if (data.default ~= nil and (value.min ~= data.default or value.max ~= data.default)) or data.default == nil then
Line 3,728: Line 3,728:
                     tpl_args._properties[data.property .. range_data.property] = value[short_key]
                     tpl_args._properties[data.property .. range_data.property] = value[short_key]
                 end
                 end
               
 
                 -- process to HTML to use on list pages or other purposes
                 -- process to HTML to use on list pages or other purposes
                 h.handle_range_args(tpl_args, frame, k, data.property, value, data.html_fmt_options or {})
                 h.handle_range_args(tpl_args, frame, k, data.property, value, data.html_fmt_options or {})
             end
             end
           
 
             for short_key, range_data in pairs(h.range_map) do
             for short_key, range_data in pairs(h.range_map) do
                 tpl_args[k .. range_data.var] = value[short_key]
                 tpl_args[k .. range_data.var] = value[short_key]
Line 3,746: Line 3,746:
                 max = {},
                 max = {},
             }
             }
       
 
             for var_type, value in pairs(damage) do
             for var_type, value in pairs(damage) do
                 -- covers the min/max/avg range
                 -- covers the min/max/avg range
Line 3,762: Line 3,762:
                 value[short_key] = result
                 value[short_key] = result
                 tpl_args[string.format('%s%s', data.name, range_data.var)] = result
                 tpl_args[string.format('%s%s', data.name, range_data.var)] = result
                 -- It will set the property, even if 0.  
                 -- It will set the property, even if 0.
                 -- Not sure if it is better to not set it, but on the other hand this way it can be queried for having no dps of a particular type
                 -- Not sure if it is better to not set it, but on the other hand this way it can be queried for having no dps of a particular type
                 tpl_args._properties[string.format('Has %s%s', data.property, range_data.property)] = result
                 tpl_args._properties[string.format('Has %s%s', data.property, range_data.property)] = result
             end
             end
           
 
             if value.avg > 0 then
             if value.avg > 0 then
                 h.handle_range_args(tpl_args, frame, data.name, 'Has ' .. data.property, value, data.html_fmt_options or {})
                 h.handle_range_args(tpl_args, frame, data.name, 'Has ' .. data.property, value, data.html_fmt_options or {})
Line 3,772: Line 3,772:
         end
         end
     end
     end
   
 
     -- late processing
     -- late processing
     core.process_arguments(tpl_args, frame, {array=core.late_args})
     core.process_arguments(tpl_args, frame, {array=core.late_args})
     core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].late_args})
     core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].late_args})
   
 
     -- Setting semantic properties Part 1 (base values)
     -- Setting semantic properties Part 1 (base values)
   
 
     local val
     local val
   
 
     for _, k in ipairs(tpl_args._total_args) do
     for _, k in ipairs(tpl_args._total_args) do
         local prop = core.map[k].property
         local prop = core.map[k].property
Line 3,791: Line 3,791:
         end
         end
     end
     end
   
 
     util.smw.set(frame, tpl_args._properties)
     util.smw.set(frame, tpl_args._properties)
   
 
     -- Subobjects
     -- Subobjects
     local command
     local command
Line 3,803: Line 3,803:
         util.smw.subobject(frame, command, properties)
         util.smw.subobject(frame, command, properties)
     end
     end
   
 
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
     -- Infobox handling  
     -- Infobox handling
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
   
 
     tpl_args._properties = {}
     tpl_args._properties = {}
     local container = mw.html.create('span')
     local container = mw.html.create('span')
         :attr( 'class', 'item-box -' .. tpl_args.frame_type)
         :attr( 'class', 'item-box -' .. tpl_args.frame_type)
   
 
     if tpl_args.class == 'Divination Card' then
     if tpl_args.class == 'Divination Card' then
         --TODO div card code
         --TODO div card code
Line 3,854: Line 3,854:
             line_type = 'single'
             line_type = 'single'
         end
         end
       
 
         local name_line = tpl_args.name
         local name_line = tpl_args.name
         if tpl_args.base_item and tpl_args.base_item ~= 'Prophecy' then
         if tpl_args.base_item and tpl_args.base_item ~= 'Prophecy' then
             name_line = name_line .. '<br>' .. tpl_args.base_item
             name_line = name_line .. '<br>' .. tpl_args.base_item
         end
         end
       
 
         container
         container
             :tag('span')
             :tag('span')
Line 3,865: Line 3,865:
:wikitext( name_line )
:wikitext( name_line )
             :done()
             :done()
           
 
         local grpcont
         local grpcont
         local valid
         local valid
Line 3,872: Line 3,872:
             :attr('class', 'item-stats')
             :attr('class', 'item-stats')
             :done()
             :done()
           
 
         for _, group in ipairs(core.display_groups) do
         for _, group in ipairs(core.display_groups) do
             grpcont = {}
             grpcont = {}
Line 3,897: Line 3,897:
                 grpcont = group.func(tpl_args, frame)
                 grpcont = group.func(tpl_args, frame)
             end
             end
           
 
             if #grpcont > 0 then
             if #grpcont > 0 then
                 statcont
                 statcont
Line 3,907: Line 3,907:
         end
         end
     end
     end
   
 
     frame:callParserFunction('#set:', tpl_args._properties)
     frame:callParserFunction('#set:', tpl_args._properties)
   
 
     if tpl_args.gem_icon ~= nil then
     if tpl_args.gem_icon ~= nil then
         container:wikitext(string.format('[[%s]]', tpl_args.gem_icon))
         container:wikitext(string.format('[[%s]]', tpl_args.gem_icon))
     end
     end
   
 
     -- Store the infobox so it can be accessed with ease on other pages
     -- Store the infobox so it can be accessed with ease on other pages
     frame:callParserFunction('#set:', {['Has infobox HTML'] = tostring(container)})
     frame:callParserFunction('#set:', {['Has infobox HTML'] = tostring(container)})
   
 
     if tpl_args.inventory_icon ~= nil then
     if tpl_args.inventory_icon ~= nil then
         container:wikitext(string.format('[[%s]]', tpl_args.inventory_icon))
         container:wikitext(string.format('[[%s]]', tpl_args.inventory_icon))
     end
     end
   
 
     local infobox = mw.html.create('span')
     local infobox = mw.html.create('span')
     infobox
     infobox
         :attr('class', 'infobox-page-container')
         :attr('class', 'infobox-page-container')
         :node(container)
         :node(container)
       
 
     if tpl_args.skill_screenshot then
     if tpl_args.skill_screenshot then
         infobox:wikitext(string.format('<br>[[%s|300px]]', tpl_args.skill_screenshot))
         infobox:wikitext(string.format('<br>[[%s|300px]]', tpl_args.skill_screenshot))
     end
     end
   
 
     local out = tostring(infobox)
     local out = tostring(infobox)
   
 
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
     -- Category handling  
     -- Category handling
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
   
 
     local cats = {}
     local cats = {}
     if tpl_args.rarity == 'Unique' then
     if tpl_args.rarity == 'Unique' then
Line 3,944: Line 3,944:
         cats[#cats+1] = tpl_args.class
         cats[#cats+1] = tpl_args.class
     end
     end
   
 
     for _, attr in ipairs(m_game.constants.attributes) do
     for _, attr in ipairs(m_game.constants.attributes) do
         if tpl_args[attr.long_lower .. '_percent'] then
         if tpl_args[attr.long_lower .. '_percent'] then
Line 3,950: Line 3,950:
         end
         end
     end
     end
   
 
     local suffix
     local suffix
     if tpl_args.class == 'Active Skill Gems' or tpl_args.class == 'Support Skill Gems' then
     if tpl_args.class == 'Active Skill Gems' or tpl_args.class == 'Support Skill Gems' then
Line 3,960: Line 3,960:
         end
         end
     end
     end
   
 
     if #tpl_args.alternate_art_inventory_icons > 0 then
     if #tpl_args.alternate_art_inventory_icons > 0 then
         cats[#cats+1] = 'Items with alternate artwork'
         cats[#cats+1] = 'Items with alternate artwork'
     end
     end
   
 
     -- TODO: add maintenance categories
     -- TODO: add maintenance categories
   
 
     if tpl_args.release_version == nil then
     if tpl_args.release_version == nil then
         cats[#cats+1] = 'Items without a release version'
         cats[#cats+1] = 'Items without a release version'
     end
     end
   
 
     if tpl_args._flags.text_modifier then
     if tpl_args._flags.text_modifier then
         cats[#cats+1] = 'Items with improper modifiers'
         cats[#cats+1] = 'Items with improper modifiers'
     end
     end
   
 
     out = out .. util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
     out = out .. util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
      
 
     --
     --
     --
    --
     --
     --


     mw.logObject(os.clock() - t)
     mw.logObject(os.clock() - t)
   
 
     return out
     return out
end
end
Line 3,994: Line 3,994:
     -- Args/Frame
     -- Args/Frame
     --
     --
   
 
     local tpl_args = getArgs(frame, {
     local tpl_args = getArgs(frame, {
         parentFirst = true,
         parentFirst = true,
Line 4,000: Line 4,000:
     })
     })
     frame = util.misc.get_frame(frame)
     frame = util.misc.get_frame(frame)
   
 
     -- Backwards compability
     -- Backwards compability
     tpl_args.item_name = tpl_args.item_name or tpl_args[1]
     tpl_args.item_name = tpl_args.item_name or tpl_args[1]
Line 4,007: Line 4,007:
     end
     end
     tpl_args.name = tpl_args.name or tpl_args[2]
     tpl_args.name = tpl_args.name or tpl_args[2]
   
 
     if util.table.has_all_value(tpl_args, {'page', 'item_name', 'item_name_exact'}) then
     if util.table.has_all_value(tpl_args, {'page', 'item_name', 'item_name_exact'}) then
         error('page, item_name or item_name_exact must be specified')
         error('page, item_name or item_name_exact must be specified')
     end
     end
   
 
     tpl_args.large = util.cast.boolean(tpl_args.large)
     tpl_args.large = util.cast.boolean(tpl_args.large)
   
 
     local img
     local img
     local result
     local result
   
 
     if util.table.has_one_value(tpl_args, core.item_link_params, nil) or tpl_args.item_name ~= nil then
     if util.table.has_one_value(tpl_args, core.item_link_params, nil) or tpl_args.item_name ~= nil then
         local query = {}
         local query = {}
       
 
         if tpl_args.page ~= nil then
         if tpl_args.page ~= nil then
             -- TODO returns the result even if the + format is specified.  
             -- TODO returns the result even if the + format is specified.
             query[#query+1] = string.format('[[%s]]', tpl_args.page)
             query[#query+1] = string.format('[[%s]]', tpl_args.page)
         else
         else
Line 4,029: Line 4,029:
                 query[#query+1] = string.format('[[Has name::%s]]', tpl_args.item_name_exact)
                 query[#query+1] = string.format('[[Has name::%s]]', tpl_args.item_name_exact)
             end
             end
           
 
             query[#query] = query[#query] .. ' [[Has inventory icon::+]] [[Has infobox HTML::+]]'
             query[#query] = query[#query] .. ' [[Has inventory icon::+]] [[Has infobox HTML::+]]'
           
 
             if tpl_args.link_type == 'skill' then
             if tpl_args.link_type == 'skill' then
                 query[#query] = query[#query] .. ' [[Concept:Skill gems]]'
                 query[#query] = query[#query] .. ' [[Concept:Skill gems]]'
             end
             end
         end
         end
       
 
         query[#query+1] = '?Has name'
         query[#query+1] = '?Has name'
         query[#query+1] = '?Has inventory icon'
         query[#query+1] = '?Has inventory icon'
Line 4,043: Line 4,043:
         query[#query+1] = '?Has inventory width'
         query[#query+1] = '?Has inventory width'
         query[#query+1] = '?Has inventory height'
         query[#query+1] = '?Has inventory height'
       
 
         -- attributes
         -- attributes
         result = util.smw.query(query, frame)
         result = util.smw.query(query, frame)
       
 
         local err
         local err
         if #result == 0 then
         if #result == 0 then
             err = util.misc.raise_error_or_return{raise_required=true, args=tpl_args, msg=string.format(
             err = util.misc.raise_error_or_return{raise_required=true, args=tpl_args, msg=string.format(
                 'No results found for search parameter "%s".',  
                 'No results found for search parameter "%s".',
                 tpl_args.page or tpl_args.item_name or tpl_args.item_name_exact  
                 tpl_args.page or tpl_args.item_name or tpl_args.item_name_exact
             )}
             )}
         elseif #result > 1 then
         elseif #result > 1 then
Line 4,059: Line 4,059:
             )}
             )}
         end
         end
       
 
         if err ~= nil then
         if err ~= nil then
             return err .. core.item_link_broken_cat
             return err .. core.item_link_broken_cat
         end
         end
       
 
         result = result[1]
         result = result[1]
     else
     else
         result = {tpl_args.page or tpl_args.item_name_exact}
         result = {tpl_args.page or tpl_args.item_name_exact}
     end
     end
   
 
     for _, k in ipairs(core.item_link_params) do
     for _, k in ipairs(core.item_link_params) do
         local prop = core.map[k].property
         local prop = core.map[k].property
Line 4,075: Line 4,075:
         end
         end
     end
     end
   
 
     if tpl_args.image ~= nil then
     if tpl_args.image ~= nil then
         if result['Has alternate inventory icons'] == '' then
         if result['Has alternate inventory icons'] == '' then
Line 4,083: Line 4,083:
             ) .. core.item_link_broken_cat}
             ) .. core.item_link_broken_cat}
         end
         end
       
 
         result['Has alternate inventory icons'] = util.string.split(result['Has alternate inventory icons'], '<MANY>')
         result['Has alternate inventory icons'] = util.string.split(result['Has alternate inventory icons'], '<MANY>')
       
 
         local index = tonumber(tpl_args.image)
         local index = tonumber(tpl_args.image)
         if index ~= nil then
         if index ~= nil then
Line 4,091: Line 4,091:
         else
         else
             -- offset 1 is needed
             -- offset 1 is needed
             local suffix = string.len(' inventory icon.png') + 1  
             local suffix = string.len(' inventory icon.png') + 1
             -- add an extra offset by 1 to account for the space  
             -- add an extra offset by 1 to account for the space
             local prefix = string.len(string.sub(result['Has inventory icon'], 1, -suffix)) + 2
             local prefix = string.len(string.sub(result['Has inventory icon'], 1, -suffix)) + 2
           
 
             for _, filename in ipairs(result['Has alternate inventory icons']) do
             for _, filename in ipairs(result['Has alternate inventory icons']) do
                 if string.sub(filename, prefix, -suffix) == tpl_args.image then
                 if string.sub(filename, prefix, -suffix) == tpl_args.image then
Line 4,102: Line 4,102:
             end
             end
         end
         end
       
 
         if img == nil then
         if img == nil then
             return util.misc.raise_error_or_return{raise_required=true, args=tpl_args, msg=string.format(
             return util.misc.raise_error_or_return{raise_required=true, args=tpl_args, msg=string.format(
Line 4,112: Line 4,112:
         img = result['Has inventory icon']
         img = result['Has inventory icon']
     end
     end
   
 
     -- output
     -- output
   
 
     local container = mw.html.create('span')
     local container = mw.html.create('span')
     container:attr('class', 'inline-infobox-container')
     container:attr('class', 'inline-infobox-container')
   
 
     if not tpl_args.large then
     if not tpl_args.large then
         container:wikitext(string.format('[[%s]]', img))
         container:wikitext(string.format('[[%s]]', img))
     end
     end
     
 
     container:wikitext(string.format('[[%s|%s]]', result[1], result['Has name'] or result[1]))
     container:wikitext(string.format('[[%s|%s]]', result[1], result['Has name'] or result[1]))
       
 
     if result['Has infobox HTML'] ~= '' then
     if result['Has infobox HTML'] ~= '' then
         container
         container
Line 4,132: Line 4,132:
                 :done()
                 :done()
     end
     end
   
 
     if tpl_args.large then
     if tpl_args.large then
         local width = tonumber(result['Has inventory width']) or tonumber(tpl_args.width)
         local width = tonumber(result['Has inventory width']) or tonumber(tpl_args.width)
         local height = tonumber(result['Has inventory height']) or tonumber(tpl_args.height)
         local height = tonumber(result['Has inventory height']) or tonumber(tpl_args.height)
       
 
         if width and height then
         if width and height then
             img = string.format('[[%s|%sx%spx]]', img, width*c.image_size, height*c.image_size)
             img = string.format('[[%s|%sx%spx]]', img, width*c.image_size, height*c.image_size)
Line 4,146: Line 4,146:
             img = string.format('[[%s]]', img)
             img = string.format('[[%s]]', img)
         end
         end
   
 
         container:wikitext(img)
         container:wikitext(img)
     end
     end
       
 
     return tostring(container)
     return tostring(container)
end
end
Line 4,163: Line 4,163:
         frame.raise = true
         frame.raise = true
     end
     end
   
 
     local result
     local result


     local status, err = pcall(function ()  
     local status, err = pcall(function ()
         result = p.item_link(frame)  
         result = p.item_link(frame)
     end)
     end)
     mw.logObject(err)
     mw.logObject(err)
   
 
     if status then
     if status then
         return result
         return result
Line 4,176: Line 4,176:
         return err .. core.item_link_broken_cat
         return err .. core.item_link_broken_cat
     else
     else
        if frame[1] == nil then
            if frame.page ~= nil then
                frame[1] = frame.page
            end
        end
         return require('Module:Item').itemLink(frame)
         return require('Module:Item').itemLink(frame)
     end
     end
Line 4,195: Line 4,200:
     })
     })
     frame = util.misc.get_frame(frame)
     frame = util.misc.get_frame(frame)
   
 
     --
     --
     local args = util.string.split_args(tpl_args.userparam, {sep=', '})
     local args = util.string.split_args(tpl_args.userparam, {sep=', '})
     tpl_args.userparam = args
     tpl_args.userparam = args
   
 
     local link = p.item_link{page=tpl_args[1], name=tpl_args['?Has name'], inventory_icon=tpl_args['?Has inventory icon'] or '', html=tpl_args['?Has infobox HTML'] or ''}
     local link = p.item_link{page=tpl_args[1], name=tpl_args['?Has name'], inventory_icon=tpl_args['?Has inventory icon'] or '', html=tpl_args['?Has infobox HTML'] or ''}
     if args.format == nil then
     if args.format == nil then
Line 4,214: Line 4,219:
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- Reponsibile for subtemplates of Template:SMW item table
-- Reponsibile for subtemplates of Template:SMW item table
--  
--


-- =p.item_table{stat_column1_format='%s%%', conditions='[[Has item class::+]] [[Has subobject.Has stat id::~additional_*dexterity*]] [[Has rarity::Unique]]', stat_column1_header='Dexterity', stat_column1_stat_format='add', stat_column1_format='Test %s', stat_column1_stat1_id='additional_dexterity', stat_column1_stat2_id='additional_strength_and_dexterity'}
-- =p.item_table{stat_column1_format='%s%%', conditions='[[Has item class::+]] [[Has subobject.Has stat id::~additional_*dexterity*]] [[Has rarity::Unique]]', stat_column1_header='Dexterity', stat_column1_stat_format='add', stat_column1_format='Test %s', stat_column1_stat1_id='additional_dexterity', stat_column1_stat2_id='additional_strength_and_dexterity'}
Line 4,223: Line 4,228:
         })
         })
     frame = util.misc.get_frame(frame)
     frame = util.misc.get_frame(frame)
   
 
     local row_infos = {}
     local row_infos = {}
     for _, row_info in ipairs(core.result.generic_item) do
     for _, row_info in ipairs(core.result.generic_item) do
Line 4,230: Line 4,235:
         end
         end
     end
     end
   
 
     -- Parse stat arguments
     -- Parse stat arguments
     local stat_columns = {}
     local stat_columns = {}
Line 4,238: Line 4,243:
     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 4,247: Line 4,252:
             stats = {},
             stats = {},
         }
         }
       
 
         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 4,268: Line 4,273:
             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 4,294: Line 4,299:
                         end
                         end
                     end
                     end
                   
 
                     if num_stats ~= #stat_texts then
                     if num_stats ~= #stat_texts then
                         tr:wikitext(util.html.td.na())
                         tr:wikitext(util.html.td.na())
Line 4,304: Line 4,309:
                             text = table.concat(stat_texts, ', ')
                             text = table.concat(stat_texts, ', ')
                         end
                         end
                   
 
                         tr:tag('td')
                         tr:tag('td')
                             :attr('data-sort-value', avg)
                             :attr('data-sort-value', avg)
Line 4,324: Line 4,329:
                         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.avg)
                         :attr('data-sort-value', total_stat.avg)
Line 4,341: Line 4,346:
         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 query = {tpl_args.conditions}
     local query = {tpl_args.conditions}
     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
   
 
     --Override and set defaults
     --Override and set defaults
     query.limit = 1000
     query.limit = 1000
   
 
     -- Set required fields
     -- Set required fields
     query[#query+1] = '?Has name#'
     query[#query+1] = '?Has name#'
Line 4,373: Line 4,378:
     end
     end
     local results = util.smw.query(query, frame)
     local results = util.smw.query(query, frame)
   
 
     if #results == 0 and tpl_args.default ~= nil then
     if #results == 0 and tpl_args.default ~= nil then
         return tpl_args.default
         return tpl_args.default
     end
     end
   
 
     if #stat_columns > 0 then
     if #stat_columns > 0 then
         local continue = true
         local continue = true
Line 4,397: Line 4,402:
                         avg = tonumber(row['Has average stat value']),
                         avg = tonumber(row['Has average stat value']),
                     }
                     }
                   
 
                     local page = util.string.split(row[1], '#')[1]
                     local page = util.string.split(row[1], '#')[1]
                     if stat_results[page] == nil then
                     if stat_results[page] == nil then
Line 4,406: Line 4,411:
                 end
                 end
             end
             end
           
 
             -- stop iteration if we didn't hit the query limit
             -- stop iteration if we didn't hit the query limit
             if #temp == 1000 then
             if #temp == 1000 then
Line 4,415: Line 4,420:
         end
         end
     end
     end
   
 
     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')
   
 
     -- Header
     -- Header
   
 
     local tr = tbl:tag('tr')
     local tr = tbl:tag('tr')
     tr
     tr
Line 4,426: Line 4,431:
             :wikitext(tpl_args.header or 'Item')
             :wikitext(tpl_args.header or 'Item')
             :done()
             :done()
           
 
     for _, row_info in ipairs(row_infos) do
     for _, row_info in ipairs(row_infos) do
         tr
         tr
Line 4,434: Line 4,439:
                 :done()
                 :done()
     end
     end
   
 
     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 = {
             page=row[1],  
             page=row[1],
             name=row['Has name'],  
             name=row['Has name'],
             inventory_icon=row['Has inventory icon'],  
             inventory_icon=row['Has inventory icon'],
             html=row['Has infobox HTML'],
             html=row['Has infobox HTML'],
             width=row['Has inventory width'],
             width=row['Has inventory width'],
             height=row['Has inventory height'],
             height=row['Has inventory height'],
         }
         }
       
 
         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(p.item_link(il_args))
                 :wikitext(p.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 4,473: Line 4,478:
         end
         end
     end
     end
   
 
     return tostring(tbl)
     return tostring(tbl)
end
end
Line 4,483: Line 4,488:
     --  data_array
     --  data_array
     --  header
     --  header
   
 
     return function (frame)
     return function (frame)
         -- Args
         -- Args
Line 4,490: Line 4,495:
         })
         })
         frame = util.misc.get_frame(frame)
         frame = util.misc.get_frame(frame)
       
 
         --
         --
         tpl_args.userparam = util.string.split_args(tpl_args.userparam, {sep=', '})
         tpl_args.userparam = util.string.split_args(tpl_args.userparam, {sep=', '})
       
 
         local tr = mw.html.create('tr')
         local tr = mw.html.create('tr')
         tr
         tr
Line 4,499: Line 4,504:
                 :wikitext(args.header)
                 :wikitext(args.header)
                 :done()
                 :done()
               
 
         for _, rowinfo in ipairs(args.data_array) do
         for _, rowinfo in ipairs(args.data_array) do
             if rowinfo.arg == nil or tpl_args.userparam[rowinfo.arg] then
             if rowinfo.arg == nil or tpl_args.userparam[rowinfo.arg] then
Line 4,508: Line 4,513:
             end
             end
         end
         end
       
 
         return '<table class="wikitable sortable item-table">' .. tostring(tr)
         return '<table class="wikitable sortable item-table">' .. tostring(tr)
     end
     end
Line 4,514: Line 4,519:


-- Base test: =p.item_list_row{userparam='weapon', 'Clarity', ['?Has name'] = 'Clarity', ['?Has inventory icon'] = 'Clarity inventory icon.png', ['?Has infobox HTML'] = 'x', ['?Has inventory width'] = '1', ['?Has inventory height'] = '1'}
-- Base test: =p.item_list_row{userparam='weapon', 'Clarity', ['?Has name'] = 'Clarity', ['?Has inventory icon'] = 'Clarity inventory icon.png', ['?Has infobox HTML'] = 'x', ['?Has inventory width'] = '1', ['?Has inventory height'] = '1'}
-- =p.item_list_row{userparam='weapon=1', ['?Has base minimum physical damage'] = '5', ['?Has minimum physical damage range average'] = '5', ['?Has minimum physical damage range minimum'] = '5', ['?Has minimum physical damage range maximum'] = '5', ['?Has base maximum physical damage'] = '6', ['?Has maximum physical damage range average'] = '8', ['?Has maximum physical damage range minimum'] = '7', ['?Has maximum physical damage range maximum'] = '9', 'Clarity', ['?Has name'] = 'Clarity', ['?Has inventory icon'] = 'Clarity inventory icon.png', ['?Has infobox HTML'] = 'x', ['?Has inventory width'] = '1', ['?Has inventory height'] = '1'}  
-- =p.item_list_row{userparam='weapon=1', ['?Has base minimum physical damage'] = '5', ['?Has minimum physical damage range average'] = '5', ['?Has minimum physical damage range minimum'] = '5', ['?Has minimum physical damage range maximum'] = '5', ['?Has base maximum physical damage'] = '6', ['?Has maximum physical damage range average'] = '8', ['?Has maximum physical damage range minimum'] = '7', ['?Has maximum physical damage range maximum'] = '9', 'Clarity', ['?Has name'] = 'Clarity', ['?Has inventory icon'] = 'Clarity inventory icon.png', ['?Has infobox HTML'] = 'x', ['?Has inventory width'] = '1', ['?Has inventory height'] = '1'}


function item_table_factory.row(args)
function item_table_factory.row(args)
Line 4,525: Line 4,530:
         })
         })
         frame = util.misc.get_frame(frame)
         frame = util.misc.get_frame(frame)
       
 
         --
         --
         tpl_args.userparam = util.string.split_args(tpl_args.userparam, {sep=', '})
         tpl_args.userparam = util.string.split_args(tpl_args.userparam, {sep=', '})
       
 
         local tr = mw.html.create('tr')
         local tr = mw.html.create('tr')
       
 
         local il_args = {
         local il_args = {
             page=tpl_args[1],  
             page=tpl_args[1],
             name=tpl_args['?Has name'],  
             name=tpl_args['?Has name'],
             inventory_icon=tpl_args['?Has inventory icon'],  
             inventory_icon=tpl_args['?Has inventory icon'],
             html=tpl_args['?Has infobox HTML'],
             html=tpl_args['?Has infobox HTML'],
             width=tpl_args['?Has inventory width'],
             width=tpl_args['?Has inventory width'],
Line 4,542: Line 4,547:
             il_args.large = tpl_args.userparam.large
             il_args.large = tpl_args.userparam.large
         end
         end
       
 
         tr
         tr
             :tag('td')
             :tag('td')
                 :wikitext(p.item_link(il_args))
                 :wikitext(p.item_link(il_args))
                 :done()
                 :done()
               
 
         for _, rowinfo in ipairs(args.data_array) do
         for _, rowinfo in ipairs(args.data_array) do
             if rowinfo.arg == nil or tpl_args.userparam[rowinfo.arg] then
             if rowinfo.arg == nil or tpl_args.userparam[rowinfo.arg] then
Line 4,560: Line 4,565:
             end
             end
         end
         end
       
 
         return tostring(tr)
         return tostring(tr)
     end
     end
Line 4,580: Line 4,585:
     })
     })
     frame = util.misc.get_frame(frame)
     frame = util.misc.get_frame(frame)
   
 
     if tpl_args.class == 'Support Skill Gems' then
     if tpl_args.class == 'Support Skill Gems' then
     elseif tpl_args.class == 'Active Skill Gems' then
     elseif tpl_args.class == 'Active Skill Gems' then
Line 4,595: Line 4,600:
     query.limit = 5000
     query.limit = 5000
     query.sort = 'Has name'
     query.sort = 'Has name'
   
 
     local results = util.smw.query(query, frame)
     local results = util.smw.query(query, frame)
   
 
     local tags = {}
     local tags = {}
   
 
     for _, row in ipairs(results) do
     for _, row in ipairs(results) do
         row['Has gem tags'] = util.string.split(row['Has gem tags'], '<MANY>')
         row['Has gem tags'] = util.string.split(row['Has gem tags'], '<MANY>')
Line 4,609: Line 4,614:
         end
         end
     end
     end
   
 
     local tags_sorted = {}
     local tags_sorted = {}
     for tag, _ in pairs(tags) do
     for tag, _ in pairs(tags) do
Line 4,615: Line 4,620:
     end
     end
     table.sort(tags_sorted)
     table.sort(tags_sorted)
   
 
     local tbl = mw.html.create('table')
     local tbl = mw.html.create('table')
     tbl
     tbl
Line 4,627: Line 4,632:
                 :done()
                 :done()
             :done()
             :done()
   
 
     for _, tag in ipairs(tags_sorted) do
     for _, tag in ipairs(tags_sorted) do
         local rows = tags[tag]
         local rows = tags[tag]
Line 4,634: Line 4,639:
             :tag('td')
             :tag('td')
                 :wikitext(tag)
                 :wikitext(tag)
           
 
         local td = tr:tag('td')
         local td = tr:tag('td')
         for i, row in ipairs(rows) do
         for i, row in ipairs(rows) do
Line 4,643: Line 4,648:
         end
         end
     end
     end
 
 
     return tostring(tbl)
     return tostring(tbl)
end
end
Line 4,651: Line 4,656:
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------


--  
--
-- Template:Item class
-- Template:Item class
--
--
Line 4,661: Line 4,666:
     })
     })
     frame = util.misc.get_frame(frame)
     frame = util.misc.get_frame(frame)
 
if not doInfoCard then
if not doInfoCard then
doInfoCard = require('Module:Infocard')._main
doInfoCard = require('Module:Infocard')._main
end
end
   
 
     core.factory.table_cast('name', {key='full', tbl=m_game.constants.item.class})(tpl_args, frame)
     core.factory.table_cast('name', {key='full', tbl=m_game.constants.item.class})(tpl_args, frame)
   
 
     if tpl_args.name_list ~= nil then
     if tpl_args.name_list ~= nil then
         tpl_args.name_list = util.string.split(tpl_args.name_list, ', ')
         tpl_args.name_list = util.string.split(tpl_args.name_list, ', ')
Line 4,673: Line 4,678:
         tpl_args.name_list = {}
         tpl_args.name_list = {}
     end
     end
   
 
     --
     --
   
 
     local ul = mw.html.create('ul')
     local ul = mw.html.create('ul')
     for _, item in ipairs(tpl_args.name_list) do
     for _, item in ipairs(tpl_args.name_list) do
Line 4,683: Line 4,688:
                 :done()
                 :done()
     end
     end
   
 


     -- Output Infocard
     -- Output Infocard
   
 
     local tplargs = {
     local tplargs = {
         ['header'] = tpl_args.name,
         ['header'] = tpl_args.name,
Line 4,692: Line 4,697:
         [1] = 'Also referred to as:' .. tostring(ul),
         [1] = 'Also referred to as:' .. tostring(ul),
     }
     }
   
 
     -- cats
     -- cats
   
 
     local cats = {
     local cats = {
         'Item classes',
         'Item classes',
         tpl_args.name,
         tpl_args.name,
     }
     }
   
 
     -- Done
     -- Done
   
 
     return doInfoCard(tplargs) .. util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
     return doInfoCard(tplargs) .. util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
end
end
Line 4,715: Line 4,720:
             mw.logObject(string.format('%s %s', data.property, prop))
             mw.logObject(string.format('%s %s', data.property, prop))
         end
         end
       
 
         for _, data in ipairs(core.dps_map) do
         for _, data in ipairs(core.dps_map) do
             mw.logObject(string.format('Has %s %s', data.property, prop))
             mw.logObject(string.format('Has %s %s', data.property, prop))
Line 4,725: Line 4,730:
     keys = {}
     keys = {}
     for _, data in ipairs(core.result.generic_item) do
     for _, data in ipairs(core.result.generic_item) do
         keys[#keys+1] = string.format("['%s'] = '1'", data.arg)  
         keys[#keys+1] = string.format("['%s'] = '1'", data.arg)
     end
     end
   
 
   return table.concat(keys, ', ')
   return table.concat(keys, ', ')
end
end


return p
return p

Revision as of 21:57, 12 January 2017

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


This module is used on 13,000+ 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 module implements {{item}}.

Overview

This module is responsible for creating item boxes, various item lists, item links and other item-related tasks. In the process a lot of the input data is verified and also added as semantic property to pages; as such, any templates deriving from this module should not be used on user pages other then for temporary testing purposes.

This template is also backed by an export script in PyPoE which can be used to export item data from the game files which then can be used on the wiki. Use the export when possible. de:Modul:Item2 ru:Модуль:Item2

-- SMW reworked item module

-- ----------------------------------------------------------------------------
-- TODO
-- ----------------------------------------------------------------------------
-- Items
-- -----
--
-- DROP restriction improvements:
--  drop location (area)
--  drop difficulty
--  drop monster type(s)
--  drop league restriction
--
-- divination card support
--
-- talismans:
--  tier
--  category
--
-- unique items:
--  3D art
--  supporter attribution
--
-- potentially include quality bonus in calculations
-- singular for weapon class in infoboxes
--
-- Maps:
--  Area level can be retrieved eventually
--  Hallowed Ground has changed area level from mod
--
-- Essence:
--  type column
--  monster modifier info
--
-- Weapons:
--  show DPS in infobox; hover?
--
-- ----------
-- Item table
-- ----------
-- adopt the new system for gems
-- ----------
-- Item class
-- ----------
--
-- remove the ul if name_list is not provided
-- maybe smw


-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------
local xtable = require('Module:Table')
local util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local m_skill = require('Module:Skill')

local p = {}
local c = {}
c.image_size = 39
-- 9 doesn't hit the query limit
c.max_mod_params = 11
c.max_stat_params = 8

-- ----------------------------------------------------------------------------
-- Other stuff
-- ----------------------------------------------------------------------------

local h = {}

function h.debug(tpl_args, func)
    if tpl_args.debug ==  nil then
        return
    end
    func()
end

function h.na_or_val(tr, value, func)
    if value == nil then
        tr:wikitext(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

-- helper to loop over the range variables easier
h.range_map = {
    min = {
        property = ' range minimum',
        var = '_range_minimum',
    },
    max = {
        property = ' range maximum',
        var = '_range_maximum',
    },
    avg = {
        property = ' range average',
        var = '_range_average',
    },
}

function h.handle_range_args(tpl_args, frame, argument_key, property, value, fmt_options)
    fmt_options = mw.clone(fmt_options)
    fmt_options.return_color = true
    local html, colour = h.format_value(tpl_args, frame, value, fmt_options)
    tpl_args[argument_key .. '_html'] = html
    tpl_args._properties[property .. ' HTML'] = html
    tpl_args._properties[property .. ' range colour'] = colour

    fmt_options = mw.clone(fmt_options)
    fmt_options.no_color = true
    tpl_args._properties[property .. ' range text'] = h.format_value(tpl_args, frame, value, fmt_options)
end

function h.stats_update(tpl_args, id, value, modid, key)
    if tpl_args[key][id] == nil then
        tpl_args[key][id] = {
            references = {modid},
            min = value.min,
            max = value.max,
            avg = value.avg,
        }
    else
        if modid ~= nil then
            table.insert(tpl_args[key][id].references, modid)
        end
        tpl_args[key][id].min = tpl_args[key][id].min + value.min
        tpl_args[key][id].max = tpl_args[key][id].max + value.max
        tpl_args[key][id].avg = tpl_args[key][id].avg + value.avg
    end
end

h.stat = {}
function h.stat.add (value, stat_cached)
    value.min = value.min + stat_cached.min
    value.max = value.max + stat_cached.max
end

function h.stat.more (value, stat_cached)
    value.min = value.min * (1 + stat_cached.min / 100)
    value.max = value.max * (1 + stat_cached.max / 100)
end

function h.stat.more_inverse (value, stat_cached)
    value.min = value.min / (1 + stat_cached.min / 100)
    value.max = value.max / (1 + stat_cached.max / 100)
end

h.tbl = {}

function h.tbl.range_properties(property)
    return function()
        local props = {}
        for _, prop in ipairs({'average', 'text', 'colour'}) do
            props[#props+1] = string.format('Has %s range %s', property, prop)
        end
        return props
    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, properties)
        values = {}

        for _, prop in ipairs(properties) do
            values[#values+1] = data[prop]
        end


        local td = tr:tag('td')
        td:attr('data-sort-value', table.concat(values, ', '))
        td:wikitext(table.concat(values, ', '))
        if args.colour then
            td:attr('class', 'tc -' .. args.colour)
        end
    end
end

function h.tbl.display.factory.range(args)
    -- args: table
    --  property
    return function (tr, data, properties)
        tr
            :tag('td')
                :attr('data-sort-value', data[string.format('Has %s range average', args.property)] or '-1')
                :attr('class', 'tc -' .. (data[string.format('Has %s range colour', args.property)] or 'default'))
                :wikitext(data[string.format('Has %s range text', args.property)])
                :done()
    end
end

function h.format_value(tpl_args, frame, value, options)
    -- value: table
    --  min:
    --  max:
    -- options: table
    --  fmt: formatter to use for the value instead of valfmt
    --  fmt_range: formatter to use for the range values. Default: (%s to %s)
    --  before: add this string before the coloured string with colour gray
    --  before_color: colour to use for the before string; false to disable
    --  after: add this string after the coloured string with colour gray
    --  after_color: colour to use for the after string; false to disabled-drop
    --  func: Function to adjust the value with before output
    --  color: colour code for util.html.poe_color, overrides mod colour
    --  no_color: set to true to ingore colour entirely
    --  return_color: also return colour

    if options.no_color == nil then
        if options.color then
            value.color = options.color
        elseif value.base ~= value.min or value.base ~= value.max then
            value.color = 'mod'
        else
            value.color = 'value'
        end
    end

    if options.func ~= nil then
        value.min = options.func(tpl_args, frame, value.min)
        value.max = options.func(tpl_args, frame, value.max)
    end

    if options.fmt == nil then
        options.fmt = '%s'
    elseif type(options.fmt) == 'function' then
        options.fmt = options.fmt(tpl_args, frame)
    end

    if value.min == value.max then
        value.out = string.format(options.fmt, value.min)
    else
        value.out = string.format(string.format(options.fmt_range or "(%s to %s)", options.fmt, options.fmt), value.min, value.max)
    end

    if options.no_color == nil then
        value.out = util.html.poe_color(value.color, value.out)
    end

    local text = {
        before = '',
        after = '',
    }

    for var, _ in pairs(text) do
        if type(options[var]) == 'string' then
            text[var] = options[var]
        elseif type(options[var]) == 'function' then
            text[var] = options[var](tpl_args, frame)
        end

        if text[var] ~= '' then
            local color
            if options[var .. '_color'] == nil then
                color = 'default'
            elseif options[var .. '_color'] ~= false then
                color = options[var .. '_color']
            end

            if color ~= nil then
                text[var] = util.html.poe_color(color, text[var])
            end
        end
    end

    local return_color
    if options.return_color ~= nil then
        return_color = value.color
    end
    return text.before .. value.out .. text.after, return_color
end

-- ----------------------------------------------------------------------------
-- core
-- ----------------------------------------------------------------------------

local core = {}

function core.validate_mod(tpl_args, frame, args)
    -- args:
    --  key   - implict or explicit
    --  i
    --  value
    local value = tpl_args[args.key .. args.i]
    local out = {
        result=nil,
        modid=nil,
        type=args.key,
    }

    if value ~= nil then
        table.insert(tpl_args.mods, value)
        table.insert(tpl_args[args.key .. '_mods'], value)
        out.modid = value
        --out.result = nil
        table.insert(tpl_args._mods, out)
        return true
    else
        value = tpl_args[args.key .. args.i .. '_text']
        if value ~= nil then
            tpl_args._flags.text_modifier = true
            --table.insert(tpl_args._subobjects, {
            --    ['Is mod number'] = args.i,
            --   ['Has mod text'] = value,
            --})
            out.result = value
            table.insert(tpl_args._mods, out)
            return true
        end
    end

    return false
end

function core.process_smw_mods(tpl_args, frame)
    if #tpl_args.mods == 0 then
        return
    end

    local mods = {}
    for _, mod_data in ipairs(tpl_args._mods) do
        if mod_data.result == nil then
            mods[mod_data.modid] = mod_data
        end
    end

    local results = {}
    local result
    local query
    local number_of_queries = math.ceil(#tpl_args.mods / c.max_mod_params)
    local pages = {}

    for query_number=1, number_of_queries do
        local offset = (query_number-1)*c.max_mod_params
        local mod_ids = {}
        for j=(1+offset), (c.max_mod_params+offset) do
            mod_ids[#mod_ids+1] = tpl_args.mods[j]
        end

        query = {
            string.format('[[Is mod::%s]]', table.concat(mod_ids, '||')),
            '?#=Page',
            '?Is mod#',
            '?Has mod group#',
            '?Has mod type#',
            '?Has stat text#',
            '?Has level requirement#',
        }

        result = util.smw.query(query, frame)

        -- remap this as table with modids as key
        for _, row in ipairs(result) do
            results[#results+1] = row

            local mod_data = mods[row['Is mod']]
            mod_data.result = row

            -- needed for the query
            pages[#pages+1] = row[1]
            -- needed for the mapping stats to mods
            pages[row[1]] = mod_data

            -- update item level requirement
            local keys = {'required_level_final'}
            -- only update base item requirement if this is an implicit
            if mod_data.key == 'implicit' then
                keys[#keys+1] = 'required_level'
            end

            for _, key in ipairs(keys) do
                local req = math.floor(tonumber(row['Has level requirement']) * 0.8)
                if req > tpl_args[key] then
                    tpl_args[key] = req
                end
            end
        end
    end

    -- TODO: Can items have mods twice? I dont think so, if they do it would need to be accounted for here
    if #results ~= #tpl_args.mods then
        local missing = {}
        for _, modid in ipairs(tpl_args.mods) do
            if mods[modid].result == nil then
                missing[#missing+1] = modid
            end
        end

        local duplicates = {}
        for _, row in ipairs(results) do
            if duplicates[row['Is mod']] == nil then
                duplicates[row['Is mod']] = {row['Page']}
            else
                table.insert(duplicates[row['Is mod']], row['Page'])
            end
        end

        local text = {}
        for modid, pages in pairs(duplicates) do
            if #pages <= 1 then
                duplicates[modid] = nil
            else
                text[#text+1] = string.format('%s (pages: %s)', modid, table.concat(pages, ', '))
            end
        end

        error(string.format('Number of mods found does not match number of queried mods. Not found: "%s", duplicates: "%s"', table.concat(missing, ', '), table.concat(text)))
    end

    -- fetch stats

    number_of_queries = math.ceil(#pages / c.max_stat_params)

    for query_number=1, number_of_queries do
        local offset = (query_number-1)*c.max_stat_params
        local temp_pages = {}
        for j=(1+offset), (c.max_stat_params+offset) do
            temp_pages[#temp_pages+1] = pages[j]
        end

        query = {
            string.format('[[-Has subobject::%s]] [[Has stat id::+]] [[Has minimum stat value::+]] [[Has maximum stat value::+]]', table.concat(temp_pages, '||')),
            '?Is stat number#',
            '?Has stat id#',
            '?Has minimum stat value#',
            '?Has maximum stat value#',
        }

        local stats = util.smw.query(query, frame)

        -- process and cache stats
        for _, stat in ipairs(stats) do
            local mod_data = pages[util.string.split(stat[1], '#')[1]]
            if mod_data.result.stats == nil then
                mod_data.result.stats = {stat, }
            else
                mod_data.result.stats[#mod_data.result.stats+1] = stat
            end

            local id = stat['Has stat id']
            local value = {
                min = tonumber(stat['Has minimum stat value']),
                max = tonumber(stat['Has maximum stat value']),
            }
            value.avg = (value.min+value.max)/2

            h.stats_update(tpl_args, id, value, mod_data.result['Is mod'], '_stats')
            if mod_data.type ~= 'implicit' then
                h.stats_update(tpl_args, id, value, mod_data.result['Is mod'], '_explicit_stats')
            end
        end
    end
end

function core.process_base_item(tpl_args, frame, args)
    local query = {}

    if tpl_args.base_item_id ~= nil then
        query[#query+1] = string.format('[[Has metadata id::%s]]', tpl_args.base_item_id)
    elseif tpl_args.base_item_page ~= nil then
        query[#query+1] = string.format('[[%s]]', tpl_args.base_item_page)
    elseif tpl_args.base_item ~= nil then
        query[#query+1] = string.format('[[Has name::%s]]', tpl_args.base_item)
    elseif tpl_args.rarity ~= 'Normal' then
        error(core.err{msg='Rarity is set to above normal, but base item is not set. A base item for rarities above normal is required!'})
    else
        return
    end

    if #query > 1 and tpl_args.rarity == 'Normal' then
        error(core.err{msg='Base item parameter is set, but rarity is set to normal. A rarity above normal is required!'})
    end

    query[#query] = query[#query] .. string.format('[[Has item class::%s]] [[Has rarity::Normal]]', tpl_args['class'])
    query[#query+1] = '?Has implicit mod ids#'
    query[#query+1] = '?Has metadata id'
    query[#query+1] = '?Has name'

    for _, k in ipairs(tpl_args._base_item_args) do
        if core.map[k].property ~= nil then
            query[#query+1] = string.format('?%s#', core.map[k].property)
        end
    end

    local result = util.smw.query(query, frame)

    if #result > 1 then
        error(core.err{msg='More then one result found for the specified base item. Consider using base_item_page or base_item_id to narrow down the results.'})
        -- TODO be more explicit in the error?
    end

    result = result[1]

    tpl_args.base_item_data = result
    core.process_arguments(tpl_args, frame, {array={'base_item', 'base_item_page', 'base_item_id'}})

    --Copy values..
    for _, k in ipairs(tpl_args._base_item_args) do
        local data = core.map[k]
        if data.property then
            local value = result[data.property]
            -- I can just use data.default since it will be nil if not provided. Neat! ;)
            if value ~= "" and tpl_args[k] == data.default then
                tpl_args[k] = value
                if data.property_func ~= nil then
                    data.property_func(tpl_args, frame)
                elseif data.func ~= nil then
                    data.func(tpl_args, frame)
                end
            elseif value == "" then
                h.debug(tpl_args, function ()
                    mw.logObject(string.format("Base item property not found: %s", data.property))
                end)
            elseif tpl_args[k] ~= data.default then
                h.debug(tpl_args, function ()
                    mw.logObject(string.format("Value for arg '%s' is set to something else then default: %s", k, tostring(tpl_args[k])))
                end)
            end
        elseif data.property_func ~= nil then
            -- property func is set, but not a property. Probably fetching subobjects here
            data.property_func(tpl_args, frame)
        end
    end
end

function core.process_arguments(tpl_args, frame, args)
    for _, k in ipairs(args.array) do
        local data = core.map[k]
        table.insert(tpl_args._total_args, k)
        if data.no_copy == nil then
            table.insert(tpl_args._base_item_args, k)
        end
        if data.func ~= nil then
            data.func(tpl_args, frame)
            --[[local status, err = pcall(data.func)
            -- an error was raised, return the error string instead of the template
            if not status then
                return err
            end
            ]]--
        end
        if data.default ~= nil and tpl_args[k] == nil then
            tpl_args[k] = data.default
        end
    end
end

function core.process_mod_stats(tpl_args, args)
    local lines = {}

    local skip = core.class_specifics[tpl_args.class]
    if skip then
        skip = skip.skip_stat_lines
    end

    for _, modinfo in ipairs(tpl_args._mods) do
        if modinfo.type == args.type then
            if modinfo.modid == nil then
                table.insert(lines, modinfo.result)
            else
                for _, line in ipairs(util.string.split(modinfo.result['Has stat text'], '<br>')) do
                    if line ~= '' then
                        if skip == nil then
                            table.insert(lines, line)
                        else
                            local skipped = false
                            for _, pattern in ipairs(skip) do
                                if string.match(line, pattern) then
                                    skipped = true
                                    break
                                end
                            end
                            if not skipped then
                                table.insert(lines, line)
                            end
                        end
                    end
                end
            end
        end
    end

    if #lines == 0 then
        return
    else
        return table.concat(lines, '<br>')
    end
end

function core.err(args)
    local err = mw.html.create('div')
    err
        :attr('style', 'font-color: red; font-weight: bold;')
        :wikitext(args.msg or ('Argument ' .. args.key .. ' to item template is invalid. Please check the documention for acceptable values.'))
        :done()

    return tostring(err)
end

--
-- function factory
--
core.factory = {}
function core.factory.array_table_cast(k, args)
    -- Arguments:
    --
    -- tbl
    -- errmsg
    return function (tpl_args, frame)
        local elements

        if tpl_args[k] ~= nil then
            elements = util.string.split(tpl_args[k], ', ')
            for _, element in ipairs(elements) do
                local r = util.table.find_in_nested_array{value=element, tbl=args.tbl, key='full'}
                if r == nil then
                    error(core.err{msg=string.format(args.errmsg, element)})
                end
            end
            tpl_args[k] = xtable:new(elements)
        end
    end
end

function core.factory.table_cast(k, args)
    return function (tpl_args, frame)
        args.value = tpl_args[k]
        tpl_args[k] = util.table.find_in_nested_array(args)
        if tpl_args[k] == nil then
            error(core.err{key=k})
        end
    end
end


function core.factory.number_cast(k)
    return function (tpl_args, frame)
        tpl_args[k] = tonumber(tpl_args[k])
    end
end

function core.factory.boolean_cast(k)
    return function(tpl_args, frame)
        if tpl_args[k] ~= nil then
            tpl_args[k] = util.cast.boolean(tpl_args[k])
        end
    end
end

function core.factory.percentage(k)
    return function (tpl_args, frame)
        local v = tonumber(tpl_args[k])

        if v == nil then
            return core.err{key=k}
        end

        if v < 0 or v > 100 then
            return core.err{msg=k .. ' must be in range 0-100.'}
        end

        tpl_args[k] = v
    end
end

 function core.factory.display_value(args)
    -- TODO: sane defaults for the prepend string "before" & after arguments
    --
    -- args:
    --  type: Type of the keys (nil = regular, gem = skill gems, stat = stats)
    --  options<Array>:
    --   key: key to use
    --   allow_zero: allow zero values
    --   hide_default: hide the value if this is set
    --   hide_default_key: key to use if it isn't equal to the key parameter
    --   -- from h.format_value --
    --   fmt: formatter to use for the value instead of valfmt
    --   fmt_range: formatter to use for the range values. Default: (%s to %s)
    --   before: add this string before the coloured string with colour gray
    --   after: add this string after the coloured string with colour gray
    --   func: Function to adjust the value with before output
    --   color: colour code for util.html.poe_color, overrides mod colour
    --   no_color: set to true to ingore colour entirely

    for k, default in pairs({options = {}}) do
        if args[k] == nil then
            args[k] = default
        end
    end

    return function (tpl_args, frame)
        local base_values = {}
        local temp_values = {}
        if args.type == 'gem' then
            if not core.class_groups.gems.keys[tpl_args.class] then
                return
            end
            for i, data in ipairs(args.options) do
                local value = tpl_args['static_' .. data.key]
                if value ~= nil then
                    base_values[#base_values+1] = value
                    temp_values[#temp_values+1] = {value={min=value,max=value}, index=i}
                else
                    value = {
                        min=tpl_args[string.format('level1_%s', data.key)],
                        max=tpl_args[string.format('level%s_%s', tpl_args.max_level, data.key)],
                    }
                    if value.min == nil or value.max == nil then
                    else
                        base_values[#base_values+1] = value.min
                        temp_values[#temp_values+1] = {value=value, index=i}
                    end
                end
            end
        elseif args.type == 'stat' then
            for i, data in ipairs(args.options) do
                local value = tpl_args._stats[data.key]
                if value ~= nil then
                    base_values[i] = value.min
                    temp_values[#temp_values+1] = {value=value, index=i}
                end
            end
        else
            for i, data in ipairs(args.options) do
                base_values[i] = tpl_args[data.key]
                local value = {}
                if tpl_args[data.key .. '_range_minimum'] ~= nil then
                    value.min = tpl_args[data.key .. '_range_minimum']
                    value.max = tpl_args[data.key .. '_range_maximum']
                elseif tpl_args[data.key] ~= nil then
                    value.min = tpl_args[data.key]
                    value.max = tpl_args[data.key]
                end
                if value.min == nil then
                else
                    temp_values[#temp_values+1] = {value=value, index=i}
                end
            end
        end

        local final_values = {}
        for i, data in ipairs(temp_values) do
            local opt = args.options[data.index]
            local insert = false
            if opt.hide_default == nil then
                insert = true
            elseif opt.hide_default_key == nil then
                local v = data.value
                if opt.hide_default ~= v.min and opt.hide_default ~= v.max then
                    insert = true
                end
            else
                local v = {
                    min = tpl_args[opt.hide_default_key .. '_range_minimum'],
                    max = tpl_args[opt.hide_default_key .. '_range_maximum'],
                }
                if v.min == nil or v.max == nil then
                    if opt.hide_default ~= tpl_args[opt.hide_default_key] then
                        insert = true
                    end
                elseif opt.hide_default ~= v.min and opt.hide_default ~= v.max then
                    insert = true
                end
            end

            if insert == true then
                table.insert(final_values, data)
            end
        end

        -- all zeros = dont display and return early
        if #final_values == 0 then
            return nil
        end

        local out = {}

        if args.before then
            out[#out+1] = util.html.poe_color('default', args.before)
        end

        for i, data in ipairs(final_values) do
            local value = data.value
            value.base = base_values[data.index]

            local options = args.options[data.index]

            if options.color == nil and args.type == 'gem' then
                value.color = 'value'
            end

            out[#out+1] = h.format_value(tpl_args, frame, value, options)
        end

        if args.after then
            out[#out+1] = util.html.poe_color('default', args.after)
        end

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

function core.factory.display_value_only(key)
    return function(tpl_args, frame)
        return tpl_args[key]
    end
end

function core.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 = util.html.abbr(value, args.tbl[args.key])
        end
        return value
    end
end

function core.factory.damage_html(args)
    return function(tpl_args, frame)
        if args.key ~= 'physical' then
           args.color = args.key
           args.no_color = true
        end
        local keys = {
            min=args.key .. '_damage_min',
            max=args.key .. '_damage_max',
        }
        local value = {}

        for ktype, key in pairs(keys) do
            value[ktype] = core.factory.display_value{options={ [1] = {
                key = key,
                no_color = args.no_color,
                hide_default = 0
            }}}(tpl_args, frame)
        end

        if value.min and value.max then
            value = value.min .. '&ndash;' .. value.max
            if args.color ~= nil then
                value = util.html.poe_color(args.color, value)
            end
            tpl_args[args.key .. '_damage_html'] = value
        end
    end
end

function core.factory.copy_stats(args)
    return function(tpl_args, frame)
        if tpl_args._stats[args.stat_key] then
            if tpl_args._stats[args.stat_key].min ~= tpl_args._stats[args.stat_key].max then
                error('TODO: Missing support for range item limits')
            end
            tpl_args[args.key] = tpl_args._stats[args.stat_key].min
        end
    end
end

--
-- argument mapping
--
-- format:
-- tpl_args key = {
--   no_copy = true or nil           -- When loading an base item, dont copy this key
--   property = 'prop',              -- Property associated with this key
--   property_func = function or nil -- Function to unpack the property into a native lua value.
--                                      If not specified, func is used.
--                                      If neither is specified, value is copied as string
--   func = function or nil          -- Function to unpack the argument into a native lua value and validate it.
--                                      If not specified, value will not be set.
--   default = object                -- Default value if the parameter is nil
-- }
core.map = {
    -- special params
    html = {
        no_copy = true,
        property = 'Has infobox HTML',
        func = nil,
    },
    implicit_stat_text = {
        property = 'Has implicit stat text',
        func = function(tpl_args, frame)
            tpl_args.implicit_stat_text = core.process_mod_stats(tpl_args, {type='implicit'})
        end,
    },
    explicit_stat_text = {
        property = 'Has explicit stat text',
        func = function(tpl_args, frame)
            tpl_args.explicit_stat_text = core.process_mod_stats(tpl_args, {type='explicit'})

            if tpl_args.is_talisman or tpl_args.is_corrupted then
                if tpl_args.explicit_stat_text == nil or tpl_args.explicit_stat_text == '' then
                    tpl_args.explicit_stat_text = util.html.poe_color('corrupted', 'Corrupted')
                else
                    tpl_args.explicit_stat_text = (tpl_args.explicit_stat_text or '') .. '<br>' .. util.html.poe_color('corrupted', 'Corrupted')
                end
            end
        end,
    },
    stat_text = {
        property = 'Has stat text',
        func = function(tpl_args, frame)
            local sep = ''
            if tpl_args.implicit_stat_text and tpl_args.explicit_stat_text then
                sep = string.format('<span class="item-stat-separator -%s"></span>', tpl_args.frame_type)
            end
            local text = (tpl_args.implicit_stat_text or '') .. sep .. (tpl_args.explicit_stat_text or '')

            if string.len(text) > 0 then
                tpl_args.stat_text = text
            end
        end,
    },
    -- generic
    class = {
        no_copy = true,
        property = 'Has item class',
        func = core.factory.table_cast('class', {key='full', tbl=m_game.constants.item.class}),
    },
    rarity = {
        no_copy = true,
        property = 'Has rarity',
        func = core.factory.table_cast('rarity', {key={'full', 'long_lower'}, tbl=m_game.constants.item.rarity, rtrkey='full'}),
    },
    name = {
        no_copy = true,
        property = 'Has name',
        func = nil,
    },
    size_x = {
        property = 'Has inventory width',
        func = core.factory.number_cast('size_x'),
    },
    size_y = {
        property = 'Has inventory height',
        func = core.factory.number_cast('size_y'),
    },
    drop_enabled = {
        property = 'Is drop enabled',
        func = core.factory.boolean_cast('drop_enabled'),
        default = true,
    },
    drop_level = {
        no_copy = true,
        property = 'Has drop level',
        func = core.factory.number_cast('drop_level'),
    },
    drop_level_maximum = {
        no_copy = true,
        property = 'Has maximum drop level',
        func = core.factory.number_cast('drop_level_maximum'),
    },
    required_level = {
        property = 'Has base level requirement',
        func = core.factory.number_cast('required_level'),
        default = 1,
    },
    required_level_final = {
        property = 'Has level requirement',
        func = function(tpl_args, frame)
            tpl_args.required_level_final = tpl_args.required_level
        end,
        default = 1,
    },
    required_dexterity = {
        property = 'Has base dexterity requirement',
        func = core.factory.number_cast('required_dexterity'),
        default = 0,
    },
    required_strength = {
        property = 'Has base strength requirement',
        func = core.factory.number_cast('required_strength'),
        default = 0,
    },
    required_intelligence = {
        property = 'Has base intelligence requirement',
        func = core.factory.number_cast('required_intelligence'),
        default = 0,
    },
    inventory_icon = {
        no_copy = true,
        property = 'Has inventory icon',
        func = function(tpl_args, frame)
            tpl_args.inventory_icon_id = tpl_args.inventory_icon or tpl_args.name
            tpl_args.inventory_icon = string.format('File:%s inventory icon.png', tpl_args.inventory_icon_id)
        end,
    },
    -- note: this must be called after inventory item to work correctly as it depends on tpl_args.inventory_icon_id being set
    alternate_art_inventory_icons = {
        no_copy = true,
        property = 'Has alternate inventory icons',
        func = function(tpl_args, frame)
            local icons = {}
            if tpl_args.alternate_art_inventory_icons ~= nil then
                local names = util.string.split(tpl_args.alternate_art_inventory_icons, ', ')

                for _, name in ipairs(names) do
                    icons[#icons+1] = string.format('File:%s %s inventory icon.png', tpl_args.inventory_icon_id, name)
                end
            end
            tpl_args.alternate_art_inventory_icons = icons
        end,
        default = {},
    },
    buff_icon = {
        property = 'Has buff icon',
        func = function(tpl_args, frame)
            tpl_args.buff_icon = string.format('File:%s status icon.png', tpl_args.name)
        end,
    },
    help_text = {
        property = 'Has help text',
        func = nil,
    },
    flavour_text = {
        no_copy = true,
        property = 'Has flavour text',
        func = nil,
    },
    tags = {
        property = 'Has tags',
        property_func = function(tpl_args, frame)
            tpl_args.tags = util.string.split(tpl_args.tags, '<MANY>')
        end,
        func = core.factory.array_table_cast('tags', {
            tbl = m_game.constants.tags,
            errmsg = '%s is not a valid tag',
        }),
    },
    metadata_id = {
        no_copy = true,
        property = 'Has metadata id',
        func = nil,
    },
    is_corrupted = {
        no_copy = true,
        property = 'Is corrupted',
        func = core.factory.boolean_cast('is_corrupted'),
        default = false,
    },
    --
    -- specific section
    --
    -- amulets
    is_talisman = {
        property = 'Is talisman',
        func = core.factory.boolean_cast('is_talisman'),
    },

    -- flasks
    charges_max = {
        property = 'Has base maximum flask charges',
        func = core.factory.number_cast('charges_max'),
    },
    charges_per_use = {
        property = 'Has base flask charges per use',
        func = core.factory.number_cast('charges_per_use'),
    },
    flask_mana = {
        property = 'Has base flask mana recovery',
        func = core.factory.number_cast('flask_mana'),
    },
    flask_life = {
        property = 'Has base flask life recovery',
        func = core.factory.number_cast('flask_life'),
    },
    flask_duration = {
        property = 'Has base flask duration',
        func = core.factory.number_cast('flask_duration'),
    },
    buff_id = {
        property = 'Has buff id',
        func = nil,
    },
    buff_values = {
        property = nil,
        property_func = function(tpl_args, frame)
            local results = util.smw.query({
                string.format('[[-Has subobject::%s]] [[Is number::+]] [[Has buff value::+]]', tpl_args.base_item_page),
                '?Is number#',
                '?Has buff value#',
            }, frame)
            local values = {}
            for _, row in ipairs(results) do
                local i = tonumber(row['Is number'])
                local value = tonumber(row['Has buff value'])
                values[i] = value
                if i ~= nil then
                    local key = 'buff_value' .. i
                    tpl_args[key] = value
                    tpl_args._subobjects[key] = {
                        ['Is number'] = i,
                        ['Has buff value'] = value,
                    }
                end
            end

            tpl_args.buff_values = values
        end,
        func = function(tpl_args, frame)
            local values = {}
            local i = 0
            local value
            local key
            repeat
                i = i + 1
                key = 'buff_value' .. i
                value = tonumber(tpl_args[key])
                values[i] = value
                tpl_args[key] = value
                tpl_args._subobjects[key] = {
                    ['Is number'] = i,
                    ['Has buff value'] = value,
                }
            until values[i] == nil

            tpl_args.buff_values = values
        end,
    },
    buff_stat_text = {
        property = 'Has buff stat text',
        func = nil,
    },

    -- weapons & active skills
    critical_strike_chance = {
        property = 'Has base critical strike chance',
        func = core.factory.number_cast('critical_strike_chance'),
    },
    -- weapons
    attack_speed = {
        property = 'Has base attack speed',
        func = core.factory.number_cast('attack_speed'),
    },
    physical_damage_min = {
        property = 'Has base minimum physical damage',
        func = core.factory.number_cast('physical_damage_min'),
    },
    physical_damage_max = {
        property = 'Has base maximum physical damage',
        func = core.factory.number_cast('physical_damage_max'),
    },
    range = {
        property = 'Has base weapon range',
        func = core.factory.number_cast('range'),
    },
    -- armor-type stuff
    armour = {
        property = 'Has base armour',
        func = core.factory.number_cast('armour'),
        default = 0,
    },
    energy_shield = {
        property = 'Has base energy shield',
        func = core.factory.number_cast('energy_shield'),
        default = 0,
    },
    evasion = {
        property = 'Has base evasion',
        func = core.factory.number_cast('evasion'),
        default = 0,
    },
    -- shields
    block = {
        property = 'Has base block',
        func = core.factory.number_cast('block'),
    },
    -- skill gem stuff
    gem_description = {
        property = 'Has description',
        func = nil,
    },
    dexterity_percent = {
        property = 'Has dexterity percentage',
        func = core.factory.percentage('dexterity_percent'),
    },
    strength_percent = {
        property = 'Has strength percentage',
        func = core.factory.percentage('strength_percent'),
    },
    intelligence_percent = {
        property = 'Has intelligence percentage',
        func = core.factory.percentage('intelligence_percent'),
    },
    primary_attribute = {
        property = 'Has primary attribute',
        func = function(tpl_args, frame)
            for _, attr in ipairs(m_game.constants.attributes) do
                local val = tpl_args[attr.long_lower .. '_percent']
                if val and val >= 60 then
                    tpl_args['primary_attribute'] = attr.long_upper
                    return
                end
            end
            tpl_args['primary_attribute'] = 'None'
        end,
    },
    gem_tags = {
        property = 'Has gem tags',
        -- TODO: default rework
        func = core.factory.array_table_cast('gem_tags', {
            tbl = m_game.constants.item.gem_tags,
            errmsg = '%s is not a valid tag',
        }),
        default = {},
    },
    experience = {
        property = 'Has maximum experience',
        func = core.factory.percentage('experience'),
    },
    skill_screenshot = {
        property = 'Has skill screenshot',
        func = function(tpl_args, frame)
            tpl_args.skill_screenshot = string.format('File:%s skill screenshot.jpg', tpl_args.name)
        end,
    },
    -- Active gems only
    gem_icon = {
        property = 'Has skill gem icon',
        func = function(tpl_args, frame)
            -- TODO readd support if needed.
            tpl_args.gem_icon = string.format('File:%s skill icon.png', tpl_args.name)
        end,
    },
    -- Support gems only
    support_gem_letter_html = {
        property = 'Has support gem letter HTML',
        func = function(tpl_args, frame)
            if tpl_args.support_gem_letter == nil then
                return
            end

            -- TODO replace this with a loop possibly
            local css_map = {
                strength = 'red',
                intelligence = 'blue',
                dexterity = 'green',
            }
            local id
            for k, v in pairs(css_map) do
                k = string.format('%s_percent', k)
                if tpl_args[k] and tpl_args[k] > 50 then
                    id = v
                    break
                end
            end

            if id ~= nil then
                local container = mw.html.create('span')
                container
                    :attr('class', string.format('support-gem-id-%s', id))
                    :wikitext(tpl_args.support_gem_letter)
                    :done()
                tpl_args.support_gem_letter_html = tostring(container)
            end
        end,
    },
    -- Maps
    map_tier = {
        property = 'Has map tier',
        func = core.factory.number_cast('tier'),
    },
    map_guild_character = {
        no_copy = true,
        property = 'Has map guild character',
        func = nil,
    },
    map_area_id = {
        no_copy = true,
        property = 'Has map area id',
        func = nil, -- TODO: Validate against a query?
    },
    map_area_level = {
        no_copy = true,
        property = 'Has map area level',
        func = core.factory.number_cast('map_area_level'),
    },
    unique_map_guild_character = {
        property = 'Has unique map guild character',
        property_func = function(tpl_args, frame)
            tpl_args.map_guild_character = tpl_args.unique_map_guild_character
            tpl_args.unique_map_guild_character = nil
        end,
        func = nil,
    },
    unique_map_area_id = {
        property = 'Has unique map area id',
        func = nil, -- TODO: Validate against a query?
        property_func = function(tpl_args, frame)
            tpl_args.map_area_id = tpl_args.unique_map_area_id
            tpl_args.unique_map_area_id = nil
        end,
    },
    unique_map_area_level = {
        property = 'Has unique map area level',
        func = core.factory.number_cast('unique_map_area_level'),
        property_func = function(tpl_args, frame)
            tpl_args.map_area_level = tpl_args.unique_map_area_level
            tpl_args.unique_map_area_level = nil
        end,
    },
    --
    -- Currency-like items
    --
    stack_size = {
        property = 'Has stack size',
        func = core.factory.number_cast('stack_size'),
    },
    stack_size_currency_tab = {
        property = 'Has currency tab stack size',
        func = core.factory.number_cast('stack_size_currency_tab'),
    },
    description = {
        property = 'Has description',
        func = nil,
    },
    cosmetic_type = {
        property = 'Has cosmetic type',
        func = nil,
    },
    -- for essences
    is_essence = {
        property = 'Is essence',
        func = core.factory.boolean_cast('is_essence'),
        default = false,
    },
    essence_level_restriction = {
        property = 'Has essence level restriction',
        func = core.factory.number_cast('essence_level_restriction'),
    },
    essence_tier = {
        property = 'Has essence tier',
        func = core.factory.number_cast('essence_tier'),
    },
    --
    -- hideout doodads (HideoutDoodads.dat)
    --
    is_master_doodad = {
        property = 'Is master doodad',
        func = core.factory.boolean_cast('is_master_doodad'),
    },
    master = {
        property = 'Is sold by master',
        -- todo validate against list of master names
        func = core.factory.table_cast('master', {key='full', tbl=m_game.constants.masters}),
    },
    master_level_requirement = {
        property = 'Has master level requirement',
        func = core.factory.number_cast('master_level_requirement'),
    },
    master_favour_cost = {
        property = 'Has master favour cost',
        func = core.factory.number_cast('master_favour_cost'),
    },
    variation_count = {
        property = 'Has variation count',
        func = core.factory.number_cast('variation_count'),
    },
    -- Propehcy
    prophecy_id = {
        property = 'Has prophecy id',
        func = nil,
    },
    prediction_text = {
        property = 'Has prophecy prediction text',
        func = nil,
    },
    seal_cost_normal = {
        property = 'Has prophecy seal cost in normal difficulty',
        func = core.factory.number_cast('seal_cost_normal'),
    },
    seal_cost_cruel = {
        property = 'Has prophecy seal cost in cruel difficulty',
        func = core.factory.number_cast('seal_cost_cruel'),
    },
    seal_cost_merciless = {
        property = 'Has prophecy seal cost in merciless difficulty',
        func = core.factory.number_cast('seal_cost_merciless'),
    },
    -- Divination cards
    card_art = {
        proprety = 'Has divination card art',
        func = function(tpl_args, frame)
            tpl_args.card_art = string.format('File:%s card art.png', tpl_args.name)
        end,
    },
    card_reward = {
        property = 'Has divinication card reward',
        func = nil,
    },
    -- ------------------------------------------------------------------------
    -- derived stats
    -- ------------------------------------------------------------------------

    -- For rarity != normal, rarity already verified
    base_item = {
        no_copy = true,
        property = 'Has base item',
        func = function(tpl_args, frame)
            tpl_args.base_item = tpl_args.base_item_data['Has name']
        end,
    },
    base_item_id = {
        no_copy = true,
        property = 'Has base item metadata id',
        func = function(tpl_args, frame)
            tpl_args.base_item_id = tpl_args.base_item_data['Has metadata id']
        end,
    },
    base_item_page = {
        no_copy = true,
        property = 'Has base item wiki page',
        func = function(tpl_args, frame)
            tpl_args.base_item_page = tpl_args.base_item_data[1]
        end,
    },

    name_list = {
        no_copy = true,
        property = 'Has names',
        func = function(tpl_args, frame)
            if tpl_args.name_list ~= nil then
                tpl_args.name_list = util.string.split(tpl_args.name_list, ', ')
                tpl_args.name_list[#tpl_args.name_list+1] = tpl_args.name
            else
                tpl_args.name_list = {tpl_args.name}
            end
        end,
    },
    name_list_lower = {
        no_copy = true,
        property = 'Has lowercase names',
        func = function(tpl_args, frame)
            tpl_args.name_list_lower = {}
            for index, value in ipairs(tpl_args.name_list) do
                tpl_args.name_list_lower[index] = mw.ustring.lower(value)
            end
        end,
    },
    gem_tags_difference = {
        no_copy = true,
        property = 'Has gem tags difference',
        func = function(tpl_args, frame)
            if tpl_args.gem_tags ~= nil then
                local gtags = {}
                -- copy tags
                for _, data in ipairs(m_game.constants.item.gem_tags) do
                    if data.full and data.full ~= '' then
                        gtags[data.full] = true
                    end
                end
                -- delete existing tags
                for _, tag in ipairs(tpl_args.gem_tags) do
                    gtags[tag] = nil
                end

                -- add them as ordered list and not as hash table so it is consistent with the other gem tag list
                tpl_args.gem_tags_difference = {}
                for key, value in pairs(gtags) do
                    tpl_args.gem_tags_difference[#tpl_args.gem_tags_difference+1] = key
                end
            end
        end,
    },
    frame_type = {
        no_copy = true,
        property = nil,
        func = function(tpl_args, frame)
            if tpl_args.name == 'Prophecy' or tpl_args.base_item == 'Prophecy' then
                tpl_args.frame_type = 'prophecy'
                return
            end

            local var = core.class_specifics[tpl_args.class]
            if var ~= nil and var.frame_type ~= nil then
                tpl_args.frame_type = var.frame_type
                return
            end

            tpl_args.frame_type = string.lower(tpl_args.rarity)
        end,
    },
    --
    -- args populated by mod validation
    --
    mods = {
        no_copy = true,
        property = 'Has mod ids',
        default = {},
    },
    implicit_mods = {
        property = 'Has implicit mod ids',
        property_func = function (tpl_args)
            tpl_args.implicit_mods = util.string.split(tpl_args.implicit_mods, '<MANY>')
            for _, modid in ipairs(tpl_args.implicit_mods) do
                tpl_args.mods[#tpl_args.mods+1] = modid
                tpl_args._mods[#tpl_args._mods+1] = {
                    result=nil,
                    modid=modid,
                    type='implicit',
                }
            end
        end,
        default = {},
    },
    explicit_mods = {
        no_copy = true,
        property = 'Has explicit mod ids',
        default = {},
    },
    physical_damage_html = {
        no_copy = true,
        property = nil,
        func = core.factory.damage_html{key='physical'},
    },
    fire_damage_html = {
        no_copy = true,
        property = nil,
        func = core.factory.damage_html{key='fire'},
    },
    cold_damage_html = {
        no_copy = true,
        property = nil,
        func = core.factory.damage_html{key='cold'},
    },
    lightning_damage_html = {
        no_copy = true,
        property = nil,
        func = core.factory.damage_html{key='lightning'},
    },
    chaos_damage_html = {
        no_copy = true,
        property = nil,
        func = core.factory.damage_html{key='chaos'},
    },
    damage_avg = {
        no_copy = true,
        property = 'Has average damage',
        func = function(tpl_args, frame)
            local dmg = {min=0, max=0}
            for key, _ in pairs(dmg) do
                for _, data in ipairs(m_game.constants.damage_types) do
                    dmg[key] = dmg[key] + tpl_args[string.format('%s_damage_%s_range_average', data.short_lower, key)]
                end
            end

            dmg = (dmg.min + dmg.max) / 2

            tpl_args.damage_avg = dmg
        end,
    },
    damage_html = {
        no_copy = true,
        property = 'Has damage HTML',
        func = function(tpl_args, frame)
            local text = {}
            for _, data in ipairs(m_game.constants.damage_types) do
                local value = tpl_args[data.short_lower .. '_damage_html']
                if value ~= nil then
                    text[#text+1] = value
                end
            end
            if #text > 0 then
                tpl_args.damage_html = table.concat(text, '<br>')
            end
        end,
    },
    jewel_radius = {
        no_copy = true,
        property = 'Has jewel radius',
        func = core.factory.copy_stats{key='jewel_radius', stat_key='local_jewel_effect_base_radius'}
    },
    jewel_radius_html = {
        no_copy = true,
        property = 'Has jewel radius HTML',
        func = function(tpl_args, frame)
            if tpl_args.jewel_radius then
                tpl_args.jewel_radius_html = string.format('%s (%i)', (m_game.constants.item.jewel_radius_to_size[tpl_args.jewel_radius] or '?'), tpl_args.jewel_radius)
            end
        end,
    },
}

core.stat_map = {
    range = {
        property = 'Has weapon range',
        stats_add = {
            'local_weapon_range_+',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    physical_damage_min = {
        property = 'Has minimum physical damage',
        stats_add = {
            'local_minimum_added_physical_damage',
        },
        stats_increased = {
            'local_physical_damage_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    physical_damage_max = {
        property = 'Has maximum physical damage',
        stats_add = {
            'local_maximum_added_physical_damage',
        },
        stats_increased = {
            'local_physical_damage_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    fire_damage_min = {
        default = 0,
        property = 'Has minimum fire damage',
        stats_add = {
            'local_minimum_added_fire_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'fire',
            fmt = '%i',
        },
    },
    fire_damage_max = {
        default = 0,
        property = 'Has maximum fire damage',
        stats_add = {
            'local_maximum_added_fire_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'fire',
            fmt = '%i',
        },
    },
    cold_damage_min = {
        default = 0,
        property = 'Has minimum cold damage',
        stats_add = {
            'local_minimum_added_cold_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'cold',
            fmt = '%i',
        },
    },
    cold_damage_max = {
        default = 0,
        property = 'Has maximum cold damage',
        stats_add = {
            'local_maximum_added_cold_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'cold',
            fmt = '%i',
        },
    },
    lightning_damage_min = {
        default = 0,
        property = 'Has minimum lightning damage',
        stats_add = {
            'local_minimum_added_lightning_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'lightning',
            fmt = '%i',
        },
    },
    lightning_damage_max = {
        default = 0,
        property = 'Has maximum lightning damage',
        stats_add = {
            'local_maximum_added_lightning_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'lightning',
            fmt = '%i',
        },
    },
    chaos_damage_min = {
        default = 0,
        property = 'Has minimum chaos damage',
        stats_add = {
            'local_minimum_added_chaos_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'chaos',
            fmt = '%i',
        },
    },
    chaos_damage_max = {
        default = 0,
        property = 'Has maximum chaos damage',
        stats_add = {
            'local_maximum_added_chaos_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'chaos',
            fmt = '%i',
        },
    },
    critical_strike_chance = {
        property = 'Has critical strike chance',
        stats_add = {
            'local_critical_strike_chance',
        },
        stats_increased = {
            'local_critical_strike_chance_+%',
        },
        stats_override = {
            ['local_weapon_always_crit'] = {min=100, max=100},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%.2f%%',
        },
    },
    attack_speed = {
        property = 'Has attack speed',
        stats_increased = {
            'local_attack_speed_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%.2f',
        },
    },
    flask_life = {
        property = 'Has flask life recovery',
        stats_add = {
            'local_flask_life_to_recover',
        },
        stats_increased = {
            'local_flask_life_to_recover_+%',
            'local_flask_amount_to_recover_+%',
        },
        html_fmt_options = {
            fmt = '%i',
        },
    },
    flask_mana = {
        property = 'Has flask mana recovery',
        stats_add = {
            'local_flask_mana_to_recover',
        },
        stats_increased = {
            'local_flask_mana_to_recover_+%',
            'local_flask_amount_to_recover_+%',
        },
    },
    flask_duration = {
        property = 'Has flask duration',
        stats_increased = {
            'local_flask_duration_+%',
        },
        stats_increased_inverse = {
            'local_flask_recovery_speed_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%.2f',
        },
    },
    charges_per_use = {
        property = 'Has flask charges per use',
        stats_increased = {
            'local_charges_used_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    charges_max = {
        property = 'Has maximum flask charges',
        stats_add = {
            'local_extra_max_charges',
        },
        stats_increased = {
            'local_max_charges_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    block = {
        property = 'Has block',
        stats_add = {
            'local_additional_block_chance_%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i%%',
        },
    },
    armour = {
        property = 'Has armour',
        stats_add = {
            'local_base_physical_damage_reduction_rating',
        },
        stats_increased = {
            'local_physical_damage_reduction_rating_+%',
            'local_armour_and_energy_shield_+%',
            'local_armour_and_evasion_+%',
            'local_armour_and_evasion_and_energy_shield_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    evasion = {
        property = 'Has evasion',
        stats_add = {
            'local_base_evasion_rating',
        },
        stats_increased = {
            'local_evasion_rating_+%',
            'local_evasion_and_energy_shield_+%',
            'local_armour_and_evasion_+%',
            'local_armour_and_evasion_and_energy_shield_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    energy_shield = {
        property = 'Has energy shield',
        stats_add = {
            'local_energy_shield'
        },
        stats_increased = {
            'local_energy_shield_+%',
            'local_armour_and_energy_shield_+%',
            'local_evasion_and_energy_shield_+%',
            'local_armour_and_evasion_and_energy_shield_+%',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    required_dexterity = {
        property = 'Has dexterity requirement',
        stats_add = {
            'local_dexterity_requirement_+'
        },
        stats_increased = {
            'local_dexterity_requirement_+%',
            'local_attribute_requirements_+%',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    required_intelligence = {
        property = 'Has intelligence requirement',
        stats_add = {
            'local_intelligence_requirement_+'
        },
        stats_increased = {
            'local_intelligence_requirement_+%',
            'local_attribute_requirements_+%',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    required_strength = {
        property = 'Has strength requirement',
        stats_add = {
            'local_strength_requirement_+'
        },
        stats_increased = {
            'local_strength_requirement_+%',
            'local_attribute_requirements_+%',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    map_area_level = {
        property = 'Has map area level',
        stats_override = {
            ['map_item_level_override'] = true,
        },
    },
}

core.dps_map = {
    {
        name = 'physical_dps',
        property = 'physical damage per second',
        damage_args = {'physical_damage', },
        label = util.html.abbr('pDPS', 'physical damage per second'),
        html_fmt_options = {
            color = 'value',
            fmt = '%.1f',
        },
    },
    {
        name = 'fire_dps',
        property = 'fire damage per second',
        damage_args = {'fire_damage'},
        label = util.html.abbr('Fire DPS', 'fire damage per second'),
        html_fmt_options = {
            color = 'fire',
            fmt = '%.1f',
        },
    },
    {
        name = 'cold_dps',
        property = 'cold damage per second',
        damage_args = {'cold_damage'},
        label = util.html.abbr('Cold DPS', 'cold damage per second'),
        html_fmt_options = {
            color = 'cold',
            fmt = '%.1f',
        },
    },
    {
        name = 'lightning_dps',
        property = 'lightning damage per second',
        damage_args = {'lightning_damage'},
        label = util.html.abbr('Light. DPS', 'lightning damage per second'),
        html_fmt_options = {
            color = 'lightning',
            fmt = '%.1f',
        },
    },
    {
        name = 'chaos_dps',
        property = 'chaos damage per second',
        damage_args = {'chaos_damage'},
        label = util.html.abbr('Chaos DPS', 'chaos damage per second'),
        html_fmt_options = {
            color = 'chaos',
            fmt = '%.1f',
        },
    },
    {
        name = 'elemental_dps',
        property = 'elemental damage per second',
        damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'},
        label = util.html.abbr('eDPS', 'elemental damage (i.e. fire/cold/lightning) per second'),
        html_fmt_options = {
            color = 'value',
            fmt = '%.1f',
        },
    },
    {
        name = 'poison_dps',
        property = 'poison damage per second',
        damage_args = {'physical_damage', 'chaos_damage'},
        label = util.html.abbr('Poison DPS', 'poison damage (i.e. physical/chaos) per second'),
        html_fmt_options = {
            color = 'value',
            fmt = '%.1f',
        },
    },
    {
        name = 'dps',
        property = 'damage per second',
        damage_args = {'physical_damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'},
        label = util.html.abbr('DPS', 'total damage (i.e. physical/fire/cold/lightning/chaos) per second'),
        html_fmt_options = {
            color = 'value',
            fmt = '%.1f',
        },
    },
}

-- base item is default, but will be validated later
-- Notes:
--  inventory_icon must always be before alternate_art_inventory_icons
core.default_args = {'class', 'rarity', 'name', 'name_list', 'name_list_lower', 'size_x', 'size_y', 'drop_enabled', 'drop_level', 'drop_level_maximum', 'required_level', 'required_level_final', 'inventory_icon', 'alternate_art_inventory_icons', 'flavour_text', 'help_text', 'tags', 'metadata_id', 'is_corrupted', 'mods', 'implicit_mods', 'explicit_mods'}
-- frame_type is needed in stat_text
core.late_args = {'frame_type', 'implicit_stat_text', 'explicit_stat_text', 'stat_text'}
core.prophecy_args = {'prophecy_id', 'prediction_text', 'seal_cost_normal', 'seal_cost_cruel', 'seal_cost_merciless'}
core.class_groups = {
    flasks = {
        keys = {['Life Flasks'] = true, ['Mana Flasks'] = true, ['Hybrid Flasks'] = true, ['Utility Flasks'] = true, ['Critical Utility Flasks'] = true},
        args = {'flask_duration', 'charges_max', 'charges_per_use'},
    },
    weapons = {
        keys = {['Claws'] = true, ['Daggers'] = true, ['Wands'] = true, ['One Hand Swords'] = true, ['Thrusting One Hand Swords'] = true, ['One Hand Axes'] = true, ['One Hand Maces'] = true, ['Bows'] = true, ['Staves'] = true, ['Two Hand Swords'] = true, ['Two Hand Axes'] = true, ['Two Hand Maces'] = true, ['Sceptres'] = true, ['Fishing Rods'] = true},
        args = {'required_dexterity', 'required_intelligence', 'required_strength', 'critical_strike_chance', 'attack_speed', 'physical_damage_min', 'physical_damage_max', 'range'},
        late_args = {'physical_damage_html', 'fire_damage_html', 'cold_damage_html', 'lightning_damage_html', 'chaos_damage_html', 'damage_avg', 'damage_html'},
    },
    gems = {
        keys = {['Active Skill Gems'] = true, ['Support Skill Gems'] = true},
        args = {'dexterity_percent', 'strength_percent', 'intelligence_percent', 'primary_attribute', 'gem_tags', 'gem_tags_difference'},
    },
    armor = {
        keys = {['Gloves'] = true, ['Boots'] = true, ['Body Armours'] = true, ['Helmets'] = true, ['Shields'] = true},
        args = {'required_dexterity', 'required_intelligence', 'required_strength', 'armour', 'energy_shield', 'evasion'},
    },
    stackable = {
        keys = {['Currency'] = true, ['Stackable Currency'] = true, ['Hideout Doodads'] = true, ['Microtransactions'] = true, ['Divination Card'] = true},
        args = {'stack_size', 'stack_size_currency_tab', 'description', 'cosmetic_type'},
    },
}

core.class_specifics = {
    ['Amulets'] = {
        args = {'is_talisman'},
    },
    ['Life Flasks'] = {
        args = {'flask_life'},
    },
    ['Mana Flasks'] = {
        args = {'flask_mana'},
    },
    ['Hybrid Flasks'] = {
        args = {'flask_life', 'flask_mana'},
    },
    ['Utility Flasks'] = {
        args = {'buff_id', 'buff_values', 'buff_stat_text', 'buff_icon'},
    },
    ['Critical Utility Flasks'] = {
        args = {'buff_id', 'buff_values', 'buff_stat_text', 'buff_icon'},
    },
    ['Active Skill Gems'] = {
        args = {'skill_screenshot', 'gem_icon'},
        defaults = {
            help_text = 'Place into an item socket of the right colour to gain this skill. Right click to remove from a socket.',
            size_x = 1,
            size_y = 1,
        },
        frame_type = 'gem',
    },
    ['Support Skill Gems'] = {
        args = {'support_gem_letter_html'},
        defaults = {
            help_text = 'This is a Support Gem. It does not grant a bonus to your character, but skills in sockets connected to it. Place into an item socket connected to a socket containing the Active Skill Gem you wish to augment. Right click to remove from a socket.',
            size_x = 1,
            size_y = 1,
        },
        frame_type = 'gem',
    },
    ['Shields'] = {
        args = {'block'},
    },
    ['Maps'] = {
        args = {'map_tier', 'map_guild_character', 'map_area_id', 'map_area_level', 'unique_map_area_id', 'unique_map_area_level', 'unique_map_guild_character'},
        skip_stat_lines = {
            '%d+%% increased Quantity of Items found in this Area',
            '%d+%% increased Rarity of Items found in this Area',
            '%+%d+%% Monster pack size',
            -- ranges
            '%(%d+%-%d+%)%% increased Quantity of Items found in this Area',
            '%(%d+%-%d+%)%% increased Rarity of Items found in this Area',
            '%+%(%d+%-%d+%)%% Monster pack size',
        },
    },
    ['Currency'] = {
        frame_type = 'currency',
    },
    ['Stackable Currency'] = {
        args = {'is_essence', 'essence_level_restriction', 'essence_tier'},
        frame_type = 'currency',
    },
    ['Microtransactions'] = {
        frame_type = 'currency',
    },
    ['Hideout Doodads'] = {
        args = {'is_master_doodad', 'master', 'master_level_requirement', 'master_favour_cost', 'variation_count'},
        defaults = {
            help_text = 'Right click on this item then left click on a location on the ground to create the object.',
        },
        frame_type = 'currency',
    },
    ['Jewel'] = {
        late_args = {'jewel_radius', 'jewel_radius_html'},
        defaults = {
            help_text = 'Place into an allocated Jewel Socket on the Passive Skill Tree. Right click to remove from the Socket.',
        },
        skip_stat_lines = {
            'Limited to %d+ %(Hidden%)',
            'Jewel has a radius of %d+ %(Hidden%)',
        },
    },
    ['Quest Items'] = {
        frame_type = 'quest',
    },
    ['Divination Card'] = {
        args = {'card_art', 'card_reward'},
        frame_type = 'divicard',
    },
    ['Labyrinth Item'] = {
        frame_type = 'currency',
    },
    ['Labyrinth Trinket'] = {
        args = {'description', 'buff_icon'},
        frame_type = 'currency',
    },
}

-- add defaults from class specifics and class groups
core.item_classes = {}
function core.build_item_classes()
    for _, data in ipairs(m_game.constants.item.class) do
        core.item_classes[data['full']] = {
            args = xtable:new(),
            late_args = xtable:new(),
            defaults = {},
        }
    end

    for _, row in pairs(core.class_groups) do
        for k, _ in pairs(row.keys) do
            core.item_classes[k].args:insertT(row.args)
            if row.late_args ~= nil then
                core.item_classes[k].late_args:insertT(row.late_args)
            end
        end
    end


    for k, row in pairs(core.class_specifics) do
        if row.args ~= nil then
            core.item_classes[k].args:insertT(row.args)
        end
        if row.late_args ~= nil then
            core.item_classes[k].late_args:insertT(row.late_args)
        end
        if row.defaults ~= nil then
            for key, value in pairs(row.defaults) do
                core.item_classes[k].defaults[key] = value
            end
       end
    end
end

-- GroupTable -> RowTable -> formatter function
--
--

core.display_groups = {
    -- Tags, stats, level, etc
    {
        {
            args = {'cosmetic_type'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'cosmetic_type',
                        fmt = '%s',
                        color = 'default'
                    },
                },
            },
        },
        {
            args = function(tpl_args, frame)
                if tpl_args.class == nil then
                    return false
                end

                return core.class_groups.weapons.keys[tpl_args.class] ~= nil
            end,
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'class',
                        color = 'default',
                    },
                },
            },
        },
        {
            args = {'gem_tags'},
            func = function(tpl_args, frame)
                local out = {}
                for i, tag in ipairs(tpl_args.gem_tags) do
                    out[#out+1] = string.format('[[:Category:%s (gem tag)|%s]]', tag, tag)
                end

                return table.concat(out, ', ')
            end,
        },
        {
            args = {'support_gem_letter_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'support_gem_letter_html',
                        before = 'Icon: ',
                    },
                },
            },
        },
        {
            args = {'radius'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'radius',
                        before = 'Radius: ',
                        func = core.factory.descriptor_value{key='radius_description'},
                    },
                    [2] = {
                        key = 'radius_seceondary',
                        before = ' / ',
                        func = core.factory.descriptor_value{key='radius_secondary_description'},
                    },
                    [3] = {
                        key = 'radius_tertiary',
                        before = ' / ',
                        func = core.factory.descriptor_value{key='radius_tertiary_description'},
                    },
                },
            },
        },
        -- TODO: gem level here. Maybe put max level here?
        {
            args = nil,
            func = core.factory.display_value{
                type='gem',
                options = {
                    [1] = {
                        key = 'mana_cost',
                        hide_default = 100,
                        fmt = function (tpl_args, frame)
                            if tpl_args.has_percentage_mana_cost then
                                return '%i%%'
                            else
                                return '%i'
                            end
                        end,
                        before = function (tpl_args, frame)
                            if tpl_args.has_reservation_mana_cost then
                                return 'Mana Reserved: '
                            else
                                return 'Mana Cost: '
                            end
                        end,
                    },
                },
            },
        },
        {
            args = nil,
            func = core.factory.display_value{
                type='gem',
                options = {
                    [1] = {
                        key = 'mana_multiplier',
                        hide_default = 100,
                        fmt = '%i%%',
                        before = 'Mana Multiplier: ',
                    },
                },
            },
        },
        {
            args = nil,
            func = core.factory.display_value{
                type='gem',
                options = {
                    [1] = {
                        key = 'vaal_souls_requirement',
                        hide_default = 0,
                        fmt = '%i (N) / ',
                        before = 'Souls per use: ',
                    },
                    [2] = {
                        key = 'vaal_souls_requirement',
                        hide_default = 0,
                        fmt = '%i (C) / ',
                        func = function (tpl_args, frame, value)
                            return value*1.5
                        end,
                    },
                    [3] = {
                        key = 'vaal_souls_requirement',
                        hide_default = 0,
                        fmt = '%i (M)',
                        func = function (tpl_args, frame, value)
                            return value*2
                        end,
                    },
                },
            },
        },
        {
            args = nil,
            func = core.factory.display_value{
                type='gem',
                options = {
                    [1] = {
                        key = 'vaal_stored_uses',
                        hide_default = 0,
                        fmt = '%i',
                        before = 'Can store ',
                        after = ' use(s)',
                    },
                },
            },
        },
        {
            args = nil,
            func = core.factory.display_value{
                type='gem',
                options = {
                    [1] = {
                        key = 'stored_uses',
                        hide_default = 0,
                        fmt = '%i',
                        before = 'Can store ',
                        after = ' use(s)',
                    },
                },
            },
        },
        {
            args = nil,
            func = core.factory.display_value{
                type='gem',
                options = {
                    [1] = {
                        key = 'cooldown',
                        hide_default = 0,
                        fmt = '%.2f sec',
                        before = 'Cooldown Time: ',
                    },
                },
            },
        },
        {
            args = {'cast_time'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'cast_time',
                        hide_default = 0,
                        fmt = '%.2f sec',
                        before = 'Cast Time: ',
                    },
                },
            },
        },
        {
            args = nil,
            func = core.factory.display_value{
                type='gem',
                options = {
                    [1] = {
                        key = 'critical_strike_chance',
                        hide_default = 0,
                        fmt = '%.2f%%',
                        before = 'Critical Strike Chance: ',
                    },
                },
            },
        },
        {
            args = nil,
            func = core.factory.display_value{
                type='gem',
                options = {
                    [1] = {
                        key = 'damage_effectiveness',
                        hide_default = 100,
                        fmt = '%i%%',
                        before = 'Damage Effectiveness: ',
                    },
                },
            },
        },
        {
            args = {'projectile_speed'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'projectile_speed',
                        before = 'Projectile Speed: ',
                    },
                },
            },
        },
        -- Weapon only
        {
            args = {'physical_damage_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'physical_damage_html',
                        fmt = '%s',
                        before = 'Physical Damage: '
                    },
                },
            },
        },
        {
            args = nil,
            func = function(tpl_args, frame)
                local text = ''
                for _, dtype in ipairs({'fire_damage_html', 'cold_damage_html', 'lightning_damage_html'}) do
                    local value = tpl_args[dtype]
                    if value ~= nil then
                        text = text .. ' ' .. value
                    end
                end

                if text ~= '' then
                    return 'Elemental Damage:' .. text
                else
                    return
                end
            end,
        },
        {
            args = {'chaos_damage_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'chaos_damage_html',
                        fmt = '%s',
                        before='Chaos Damage: ',
                    },
                },
            },
        },
        {
            args = {'critical_strike_chance_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'critical_strike_chance_html',
                        fmt = '%s',
                        before = 'Critical Strike Chance: ',
                    },
                },
            },
        },
        {
            args = {'attack_speed_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'attack_speed_html',
                        fmt = '%s',
                        before = 'Attacks per Second: ',
                    },
                },
            },
        },
        {
            args = {'range_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'range_html',
                        fmt = '%s',
                        before = 'Weapon Range: ',
                    },
                },
            },
        },
        -- Map only
        {
            args = {'map_area_level'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'map_area_level',
                        fmt = '%i',
                        before = 'Map Level: ',
                    },
                },
            },
        },
        {
            args = {'map_tier'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'map_tier',
                        fmt = '%i',
                        before = 'Map Tier: ',
                    },
                },
            },
        },
        {
            args = function(tpl_args, frame)
                return tpl_args.map_guild_character ~= nil and tpl_args.rarity == 'Normal'
            end,
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'map_guild_character',
                        fmt = '%s',
                        before = util.html.abbr('Guild Character', 'When used in guild creation, this map can be used for the listed character') .. ': ',
                    },
                },
            },
        },
        {
            args = function(tpl_args, frame)
                return tpl_args.unique_map_guild_character ~= nil and tpl_args.rarity == 'Unique'
            end,
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'unique_map_guild_character',
                        fmt = '%s',
                        before = util.html.abbr('Guild Character', 'When used in guild creation, this map can be used for the listed character') .. ': ',
                    },
                },
            },
        },
        {
            args = nil,
            func = core.factory.display_value{
                type = 'stat',
                options = {
                    [1] = {
                        key = 'map_item_drop_quantity_+%',
                        fmt = '+%i%%',
                        color = 'mod',
                        before = 'Item Quantity: ',
                        hide_default = 0,
                    },
                },
            },
        },
        {
            args = nil,
            func = core.factory.display_value{
                type = 'stat',
                options = {
                    [1] = {
                        key = 'map_item_drop_rarity_+%',
                        fmt = '+%i%%',
                        color = 'mod',
                        before = 'Item Rarity: ',
                        hide_default = 0,
                    },
                },
            },
        },
        {
            args = nil,
            func = core.factory.display_value{
                type = 'stat',
                options = {
                    [1] = {
                        key = 'map_pack_size_+%',
                        fmt = '+%i%%',
                        color = 'mod',
                        before = 'Monster Pack Size: ',
                        hide_default = 0,
                    },
                },
            },
        },
        -- Jewel Only
        {
            args = nil,
            func = core.factory.display_value{
                type = 'stat',
                options = {
                    [1] = {
                        key = 'local_unique_item_limit',
                        fmt = '%i',
                        before = 'Limited to: ',
                    },
                },
            },
        },
        {
            args = {'jewel_radius_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'jewel_radius_html',
                        fmt = '%s',
                        before = 'Radius: ',
                    },
                },
            },
        },
        --[[
        {
            args = nil,
            func = core.factory.display_value{
                type = 'stat',
                options = {
                    [1] = {
                        key = 'local_jewel_effect_base_radius',
                        fmt = '%s',
                        func = function(value)
                            return string.format('%s (%i)', (m_game.constants.item.jewel_radius_to_size[value] or '?'), value)
                        end,
                        before = 'Radius: ',
                    },
                },
            },
        },]]--
        -- Flask only
        {
            args = {'flask_mana_html', 'flask_duration_html'},
            --func = core.factory.display_flask('flask_mana'),
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'flask_mana_html',
                        fmt = '%s',
                        before = 'Recovers ',
                    },
                    [2] = {
                        key = 'flask_duration_html',
                        fmt = '%s',
                        before = ' Mana over ',
                        after = ' seconds',
                    },
                }
            },
        },
        {
            args = {'flask_life_html', 'flask_duration_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'flask_life_html',
                        fmt = '%s',
                        before = 'Recovers ',
                    },
                    [2] = {
                        key = 'flask_duration_html',
                        fmt = '%s',
                        before = ' Life over ',
                        after = ' seconds',
                    },
                }
            },
        },
        {
            -- don't display for mana/life flasks
            args = function(tpl_args, frame)
                for _, k in ipairs({'flask_life_html', 'flask_mana_html'}) do
                    if tpl_args[k] ~= nil then
                        return false
                    end
                end

                return tpl_args['flask_duration_html'] ~= nil
            end,
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'flask_duration_html',
                        before = 'Lasts ',
                        after = ' Seconds',
                        fmt = '%s',
                    },
                },
            },
        },
        {
            args = {'charges_per_use_html', 'charges_max_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'charges_per_use_html',
                        before = 'Consumes ',
                        fmt = '%s',
                    },
                    [2] = {
                        key = 'charges_max_html',
                        before = ' of ',
                        after = ' Charges on use',
                        fmt = '%s',
                    },
                },
            },
        },
        {
            args = {'buff_stat_text'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'buff_stat_text',
                        color = 'mod',
                    },
                },
            },
        },
        -- armor
        {
            args = {'block_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'block_html',
                        before = 'Chance to Block: ',
                        fmt = '%s',
                        hide_default = 0,
                        hide_default_key = 'block',
                    },
                },
            },
        },
        {
            args = {'armour_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'armour_html',
                        before = 'Armour: ',
                        fmt = '%s',
                        hide_default = 0,
                        hide_default_key = 'armour',
                    },
                },
            },
        },
        {
            args = {'evasion_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'evasion_html',
                        before = 'Evasion: ',
                        fmt = '%s',
                        hide_default = 0,
                        hide_default_key = 'evasion',
                    },
                },
            },
        },
        {
            args = {'energy_shield_html'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'energy_shield_html',
                        before = 'Energy Shield: ',
                        fmt = '%s',
                        hide_default = 0,
                        hide_default_key = 'energy_shield',
                    },
                },
            },
        },
        -- Misc
        {
            args = {'stack_size'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'stack_size',
                        hide_default = 1,
                        fmt = '%i',
                        before = 'Stack Size: ',
                    },
                },
            },
        },
        -- Essence stuff
        {
            args = {'essence_tier'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'essence_tier',
                        fmt = '%i',
                        before = 'Essence Tier: ',
                    },
                },
            },
        },
    },
    -- Requirements
    {
        {
            args = {'master', 'master_level_requirement'},
            func = function(tpl_args, frame)
                -- masters have been validated before
                local data
                for i, rowdata in ipairs(m_game.constants.masters) do
                    if tpl_args.master == rowdata.full then
                        data = rowdata
                        break
                    end
                end

                return util.html.poe_color('default', 'Requires ') .. string.format('[[%s|%s %s]]', data.full, data.short_upper, tpl_args.master_level_requirement)
            end
        },
        {
            args = function(tpl_args, frame)
                if tpl_args.drop_enabled == true then
                    return false
                end
                return true
            end,
            func = function(tpl_args, frame)
                local span = mw.html.create('span')
                span
                    :attr('class', 'infobox-disabled-drop')
                    :wikitext('DROP DISABLED')
                    :done()
                return tostring(span)
            end,
        },
        -- Instead of item level, show drop level if any
        {
            args = {'drop_enabled'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'drop_level',
                        fmt = '%i',
                        before = 'Drop Level: ',
                    },
                    [2] = {
                        key = 'drop_level_maximum',
                        hide_default = 100,
                        fmt = '%i',
                        before = ' / ',
                    },
                },
            },
        },
        {
            args = nil,
            func = function(tpl_args, frame)
                local opt = {
                    [1] = {
                        key = 'required_level_final',
                        hide_default = 1,
                        before = 'Level ',
                        before_color = false,
                    },
                }

                for _, attr in ipairs(m_game.constants.attributes) do
                    opt[#opt+1] = {
                        key = string.format('required_%s_html', attr['long_lower']),
                        hide_default = 0,
                        hide_default_key = string.format('required_%s', attr['long_lower']),
                        before = ', ',
                        before_color = false,
                        after = ' ' .. attr['short_upper'],
                        after_color = false,
                    }
                end

                local requirements = core.factory.display_value{options = opt}(tpl_args, frame)

                -- return early
                if requirements == nil then
                    return
                end

                requirements = string.gsub(requirements, '^, ', '')

                return util.html.poe_color('default', 'Requires ') .. requirements
            end,
        },
    },
    -- Gem description
    {
        css_class = '-textwrap tc -gemdesc',
        {
            args = {'gem_description'},
            func = core.factory.display_value_only('gem_description'),
        },
    },
    -- Gem Quality Stats
    {
        css_class = '-textwrap tc -mod',
        {
            args = {'quality_stat_text'},
            func = function(tpl_args, frame)
                lines = {}
                lines[#lines+1] = util.html.poe_color('default', 'Per 1% Quality:')
                lines[#lines+1] = tpl_args.quality_stat_text

                return table.concat(lines, '<br>')
            end,
        },
    },
    -- Gem Implicit Stats
    {
        css_class = '-textwrap tc -mod',
        {
            args = function(tpl_args, frame)
                return core.class_groups.gems.keys[tpl_args.class] and tpl_args.stat_text
            end,
            func = function(tpl_args, frame)
                lines = {}
                lines[#lines+1] = tpl_args.stat_text
                if tpl_args.gem_tags:contains('Vaal') then
                    lines[#lines+1] = util.html.poe_color('corrupted', 'Corrupted')
                end
                return table.concat(lines, '<br>')
            end,
        },
    },
    -- Implicit Stats
    {
        css_class = '-textwrap tc -mod',
        func = function(tpl_args, frame)
            if tpl_args.implicit_stat_text ~= '' then
                return {tpl_args.implicit_stat_text}
            else
                return {}
            end
        end,
    },
    -- Stats
    {
        css_class = '-textwrap tc -mod',
        func = function(tpl_args, frame)
            if tpl_args.explicit_stat_text ~= '' then
                return {tpl_args.explicit_stat_text}
            else
                return {}
            end
        end,
    },
    -- Experience
    {
        {
            args = {'experience'},
            func = core.factory.display_value{
                key = 'experience',
                options = {
                    [1] = {
                        fmt = '%i',
                    },
                },
            },
        },
    },
    -- Description (currency, doodads)
    {
        css_class = '-textwrap tc -mod',
        {
            args = {'description'},
            func = core.factory.display_value_only('description'),
        },
    },
    -- Variations (for doodads)
    {
       css_class = 'tc -mod',
        {
            args = {'variation_count'},
            func = function(tpl_args, frame)
                local txt
                if tpl_args.variation_count == 1 then
                    txt = 'Variation'
                else
                    txt = 'Variations'
                end
                return string.format('%i %s', tpl_args.variation_count, txt)
            end,
        },
    },
    -- Flavour Text
    {
        css_class = '-textwrap tc -flavour',
        {
            args = {'flavour_text'},
            func = core.factory.display_value_only('flavour_text'),
        },
    },
    -- Prophecy text
    {
        css_class = '-textwrap tc -value',
        {
            args = {'prediction_text'},
            func = core.factory.display_value_only('prediction_text'),
        },
    },
    -- Help text
    {
        css_class = '-textwrap tc -help',
        {
            args = {'help_text'},
            func = core.factory.display_value_only('help_text'),
        },
    },
    -- Cost (i.e. vendor costs)
    {
        --css_class = '',
        {
            args = {'master_favour_cost'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'master_favour_cost',
                        before = 'Favour cost: ',
                        color = 'currency',
                    },
                },
            },
        },
        {
            args = {'seal_cost_normal', 'seal_cost_cruel', 'seal_cost_merciless'},
            func = core.factory.display_value{
                options = {
                    [1] = {
                        key = 'seal_cost_normal',
                        before = 'Seal cost: <br>',
                        fmt = '%dx/',
                        color = 'currency',
                    },
                    [2] = {
                        key = 'seal_cost_cruel',
                        fmt = '%dx/',
                        color = 'currency',
                    },
                    [3] = {
                        key = 'seal_cost_merciless',
                        fmt = '%dx',
                        color = 'currency',
                        after = function (tpl_args, frame)
                            -- direct call will mess up tpl_args
                            return p.item_link{item_name_exact='Silver Coin'}
                        end,
                    },
                },
            },
        },
    },
}

core.item_link_params = {'name', 'inventory_icon', 'html'}
core.item_link_broken_cat = '[[Category:Pages with broken item links]]'

core.result = {}

-- for sort type see:
-- https://meta.wikimedia.org/wiki/Help:Sorting
core.result.generic_item = {
    {
        arg = 'base_item',
        header = 'Base Item',
        properties = {'Has base item', 'Has base item wiki page'},
        display = function(tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['Has base item'])
                    :wikitext(string.format('[[%s|%s]]', data['Has base item'], data['Has base item wiki page']))
        end,
        order = 1000,
        sort_type = 'text',
    },
    {
        arg = 'essence',
        header = 'Essence<br>Tier',
        properties = {'Has essence tier'},
        display = h.tbl.display.factory.value{},
        order = 2000,
    },
    {
        arg = 'drop_level',
        header = 'Drop<br>Level',
        properties = {'Has drop level'},
        display = h.tbl.display.factory.value{},
        order = 3000,
    },
    {
        arg = 'stack_size',
        header = 'Stack<br>Size',
        properties = {'Has stack size'},
        display = h.tbl.display.factory.value{},
        order = 4000,
    },
    {
        arg = 'stack_size_currency_tab',
        header = util.html.abbr('Tab<br>Stack<br>Size', 'Stack size in the currency stash tab'),
        properties = {'Has currency tab stack size'},
        display = h.tbl.display.factory.value{},
        order = 4001,
    },
    {
        arg = 'level',
        header = m_game.level_requirement.icon,
        properties = {'Has level requirement'},
        display = h.tbl.display.factory.value{},
        order = 5000,
    },
    {
        arg = 'ar',
        header = util.html.abbr('AR', 'Armour'),
        properties = h.tbl.range_properties('armour'),
        display = h.tbl.display.factory.range{property='armour'},
        order = 6000,
    },
    {
        arg = 'ev',
        header = util.html.abbr('EV', 'Evasion Rating'),
        properties = h.tbl.range_properties('evasion'),
        display = h.tbl.display.factory.range{property='evasion'},
        order = 6001,
    },
    {
        arg = 'es',
        header = util.html.abbr('ES', 'Energy Shield'),
        properties = h.tbl.range_properties('energy shield'),
        display = h.tbl.display.factory.range{property='energy shield'},
        order = 6002,
    },
    {
        arg = 'block',
        header = util.html.abbr('Block', 'Chance to Block'),
        properties = h.tbl.range_properties('block'),
        display = h.tbl.display.factory.range{property='block'},
        order = 6003,
    },
    {
        arg = 'physical_damage_min',
        header = util.html.abbr('Min', 'Local minimum weapon damage'),
        properties = h.tbl.range_properties('minimum physical damage'),
        display = h.tbl.display.factory.range{property='minimum physical damage'},
        order = 7000,
    },
    {
        arg = 'physical_damage_max',
        header = util.html.abbr('Max', 'Local maximum weapon damage'),
        properties = h.tbl.range_properties('maximum physical damage'),
        display = h.tbl.display.factory.range{property='maximum physical damage'},
        order = 7001,

    },
    {
        arg = 'weapon',
        header = util.html.abbr('Damage', 'Colour coded damage'),
        properties = {'Has damage HTML', 'Has average damage'},
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['Has average damage'])
                    :wikitext(data['Has damage HTML'])
        end,
        order = 8000,
    },
    {
        arg = 'weapon',
        header = util.html.abbr('APS', 'Attacks per second'),
        properties = h.tbl.range_properties('attack speed'),
        display = h.tbl.display.factory.range{property='attack speed'},
        order = 8001,
    },
    {
        arg = 'weapon',
        header = util.html.abbr('Crit', 'Local weapon critical strike'),
        properties = h.tbl.range_properties('critical strike chance'),
        display = h.tbl.display.factory.range{property='critical strike chance'},
        order = 8002,
    },
    {
        arg = 'flask_life',
        header = util.html.abbr('Life', 'Life regenerated over the flask duration'),
        properties = h.tbl.range_properties('flask life recovery'),
        display = h.tbl.display.factory.range{property='flask life recovery'},
        order = 9000,
    },
    {
        arg = 'flask_mana',
        header = util.html.abbr('Mana', 'Mana regenerated over the flask duration'),
        properties = h.tbl.range_properties('flask mana recovery'),
        display = h.tbl.display.factory.range{property='flask mana recovery'},
        order = 9001,
    },
    {
        arg = 'flask',
        header = 'Duration',
        properties = h.tbl.range_properties('flask duration'),
        display = h.tbl.display.factory.range{property='flask duration'},
        order = 9002,
    },
    {
        arg = 'flask',
        header = util.html.abbr('Usage', 'Number of charges consumed on use'),
        properties = h.tbl.range_properties('flask charges per use'),
        display = h.tbl.display.factory.range{property='flask charges per use'},
        order = 9003,
    },
    {
        arg = 'flask',
        header = util.html.abbr('Capacity', 'Maximum number of flask charges held'),
        properties = h.tbl.range_properties('maximum flask charges'),
        display = h.tbl.display.factory.range{property='maximum flask charges'},
        order = 9004,
    },
    {
        arg = 'jewel_radius',
        header = 'Radius',
        properties = {'Has jewel radius', 'Has jewel radius HTML'},
        display = function (tr, data)
            tr
                :tag('td')
                    :attr('data-sort-value', data['Has jewel radius'])
                    :wikitext(data['Has jewel radius HTML'])
        end,
        order = 10000,
    },
    {
        arg = 'map_tier',
        header = 'Map<br>Tier',
        properties = {'Has map tier'},
        display = h.tbl.display.factory.value{},
        order = 11000,
    },
    {
        arg = 'map_level',
        header = 'Map<br>Level',
        properties = {'Has map area level'},
        display = h.tbl.display.factory.value{},
        order = 11010,
    },
    {
        arg = 'map_guild_character',
        header = util.html.abbr('Char', 'Character for the guild tag'),
        properties = {'Has map guild character'},
        display = h.tbl.display.factory.value{colour='value'},
        order = 11020,
        sort_type = 'text',
    },
    {
        arg = 'buff',
        header = 'Buff Effects',
        properties = {'Has buff stat text'},
        display = h.tbl.display.factory.value{colour='mod'},
        order = 12000,
        sort_type = 'text',
    },
    {
        arg = 'stat',
        header = 'Stats',
        properties = {'Has stat text'},
        display = h.tbl.display.factory.value{colour='mod'},
        order = 12001,
        sort_type = 'text',
    },
    {
        arg = 'description',
        header = 'Effect(s)',
        properties = {'Has description'},
        display = h.tbl.display.factory.value{colour='mod'},
        order = 12002,
        sort_type = 'text',
    },
    {
        arg = 'flavour_text',
        header = 'Flavour Text',
        properties = {'Has flavour text'},
        display = h.tbl.display.factory.value{colour='flavour'},
        order = 12003,
        sort_type = 'text',
    },
    {
        arg = 'help_text',
        header = 'Help Text',
        properties = {'Has help text'},
        display = h.tbl.display.factory.value{colour='help'},
        order = 12004,
        sort_type = 'text',
    },
    {
        arg = 'buff_icon',
        header = 'Buff Icon',
        properties = {'Has buff icon'},
        display = function (tr, data, properties)
            tr
                :tag('td')
                    :wikitext(string.format('[[%s]]', data['Has buff icon']))
        end,
        order = 13000,
        sort_type = 'text',
    },
}

for i, data in ipairs(core.dps_map) do
    table.insert(core.result.generic_item, {
        arg = data.name,
        header = data.label,
        properties = h.tbl.range_properties(data.property),
        display = h.tbl.display.factory.range{property=data.property},
        order = 8100+i,
    })
end

core.result.skill_gem = {
    {
        arg = 'icon',
        header = util.html.abbr('L', 'Support gem letter.'),
        property = 'Has support gem letter HTML',
        properties = {'Has support gem letter HTML'},
        cast = nil,
        display = h.tbl.display.na_or_val,
    },
    {
        arg = 'skill_icon',
        header = 'Icon',
        property = 'Has skill gem icon',
        properties = {'Has skill gem icon'},
        cast = nil,
        display = h.tbl.display.wikilink,
    },
    {
        arg = 'description',
        header = 'Description',
        property = 'Has description',
        properties = {'Has description'},
        cast = nil,
        display = h.tbl.display.na_or_val,
    },
    {
        arg = 'level',
        header = m_game.level_requirement.icon,
        property = 'Has level requirement',
        properties = {'Has level requirement'},
        cast = tonumber,
        display = h.tbl.display.na_or_val,
    },
    {
        arg = 'crit',
        header = util.html.abbr('Crit', 'Critical Strike Chance'),
        property = 'Has critical strike chance',
        properties = {'Has critical strike chance'},
        cast = tonumber,
        display = h.tbl.display.percent,
    },
    {
        arg = 'cast_time',
        header = util.html.abbr('Cast<br>Time', 'Casting time of the skill in seconds'),
        property = 'Has cast time',
        properties = {'Has cast time'},
        cast = tonumber,
        display = h.tbl.display.seconds,
    },
    {
        arg = 'dmgeff',
        header = util.html.abbr('Dmg.<br>Eff.', 'Damage Effectiveness'),
        property = 'Has damage effectiveness',
        properties = {'Has damage effectiveness'},
        cast = tonumber,
        display = h.tbl.display.percent,
    },
    {
        arg = 'mcm',
        header = util.html.abbr('MCM', 'Mana cost multiplier - missing values indicate it changes on gem level'),
        property = 'Has mana multiplier',
        properties = {'Has mana multiplier'},
        cast = tonumber,
        display = h.tbl.display.percent,
    },
    {
        arg = 'mana',
        header = util.html.abbr('Mana', 'Mana cost'),
        property = 'Has mana cost',
        properties = {'Has mana cost'},
        cast = tonumber,
        display = function (tr, value, data)
            local appendix = ''
            if data['?Has percentage mana cost'] then
                appendix = appendix .. '%'
            end
            if data['?Has reservation mana cost'] then
                appendix = appendix .. ' ' .. util.html.abbr('R', 'reserves mana')
            end

            local str

            if value ~= nil then
                str = string.format('%d', value) .. appendix
            end
            return h.na_or_val(tr, str)
        end,
    },
    {
        arg = 'vaal',
        header = util.html.abbr('Souls', 'Vaal souls requirement in Normal/Cruel/Merciless difficulty'),
        property = 'Has vaal souls requirement',
        properties = {'Has vaal souls requirement'},
        cast = tonumber,
        display = function (tr, value, data)
            return h.na_or_val(tr, value, function (value)
                return string.format('%d / %d / %d', value, value*1.5, value*2)
            end)
        end,
    },
    {
        arg = 'vaal',
        header = util.html.abbr('Uses', 'Maximum number of stored uses'),
        property = 'Has vaal stored uses',
        properties = {'Has vaal stored uses'},
        cast = tonumber,
        display = h.tbl.display.na_or_val,
    },
    {
        arg = 'radius',
        header = util.html.abbr('R1', 'Primary radius'),
        property = 'Has primary radius',
        properties = {'Has primary radius'},
        cast = tonumber,
        display = function (tr, value, data)
            return h.na_or_val(tr, value, function (value)
                return core.factory.descriptor_value{key='?Has primary radius description', tbl=data}(nil, nil, value)
            end)
        end,
    },
    {
        arg = 'radius',
        header = util.html.abbr('R2', 'Secondary radius'),
        property = 'Has secondary radius',
        properties = {'Has secondary radius'},
        cast = tonumber,
        display = function (tr, value, data)
            return h.na_or_val(tr, value, function (value)
                return core.factory.descriptor_value{key='?Has secondary radius description', tbl=data}(nil, nil, value)
            end)
        end,
    },
    {
        arg = 'radius',
        header = util.html.abbr('R3', 'Tertiary radius'),
        property = 'Has tertiary radius',
        properties = {'Has tertiary radius'},
        cast = tonumber,
        display = function (tr, value, data)
            return h.na_or_val(tr, value, function (value)
                return core.factory.descriptor_value{key='?Has tertiary radius description', tbl=data}(nil, nil, value)
            end)
        end,
    },
}

for i, attr in ipairs(m_game.constants.attributes) do
    table.insert(core.result.generic_item, 7, {
        arg = attr.short_lower,
        header = attr.icon,
        properties = h.tbl.range_properties(string.format('%s requirement', attr.long_lower)),
        display = h.tbl.display.factory.range{property=string.format('%s requirement', attr.long_lower)},
        order = 5000+i,
    })
    table.insert(core.result.skill_gem, 3, {
        arg = attr.short_lower,
        header = attr.icon,
        property = string.format('Has %s percentage', attr.long_lower),
        cast = tonumber,
        display = function (tr, value)
            return h.na_or_val(tr, value, function (value)
                return '[[File:Yes.png|yes|link=]]'
            end)
        end,
    })
end

-- ----------------------------------------------------------------------------
-- Page views
-- ----------------------------------------------------------------------------

--
-- Template:Item
--

function p.itembox (frame)
    --
    -- Args/Frame
    --
    local t = os.clock()

    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = util.misc.get_frame(frame)

    --
    -- Shared args
    --

    core.build_item_classes()

    tpl_args._flags = {}
    tpl_args._total_args = {}
    tpl_args._base_item_args = {}
    tpl_args._mods = {}
    tpl_args._stats = {}
    tpl_args._explicit_stats = {}
    tpl_args._subobjects = {}
    tpl_args._properties = {}

    -- Using general purpose function to handle release and removal versions
    util.args.version(tpl_args, {frame=frame, set_properties=true})

    -- Must validate some argument early. It is required for future things
    core.process_arguments(tpl_args, frame, {array=core.default_args})
    core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].args})

    -- set defaults

    for k, v in pairs(core.item_classes[tpl_args.class].defaults) do
        if tpl_args[k] == nil then
            tpl_args[k] = v
        end
    end

    -- Base Item

    core.process_base_item(tpl_args, frame)

    -- Prophecy special snowflake
    if tpl_args.base_item == 'Prophecy' then
        err = core.process_arguments(tpl_args, frame, {array=core.prophecy_args})
        if err then
            return err
        end

        tpl_args.inventory_icon = 'File:Prophecy inventory icon.png'
    end

    -- Mods

    for _, k in ipairs({'implicit', 'explicit'}) do
        local success = true
        local i = 1
        while success do
            success = core.validate_mod(tpl_args, frame, {key=k, i=i})
            i = i + 1
        end
    end

    core.process_smw_mods(tpl_args, frame)

    -- Add stats - this is for when mods are not set, but we still need stats to calcuate new armour values etc
    util.args.stats(tpl_args, {prefix='extra_'})
    for _, stat in ipairs(tpl_args.extra_stats) do
        if stat.value ~= nil then
            stat.min = stat.value
            stat.max = stat.value
            stat.avg = stat.value
        end
        h.stats_update(tpl_args, stat.id, stat, nil, '_stats')
        h.stats_update(tpl_args, stat.id, stat, nil, '_explicit_stats')
    end

    -- Transpose stats into subobjects
    for id, data in pairs(tpl_args._stats) do
        tpl_args._subobjects[id] = {
            ['Has stat id'] = id,
            ['Has minimum stat value'] = data.min,
            ['Has maximum stat value'] = data.max,
            ['Has average stat value'] = data.avg,
        }
    end
    for id, data in pairs(tpl_args._explicit_stats) do
        tpl_args._subobjects['explicit_' .. id] = {
            ['Has explicit stat id'] = id,
            ['Has minimum stat value'] = data.min,
            ['Has maximum stat value'] = data.max,
            ['Has average stat value'] = data.avg,
        }
    end

    -- Handle extra stats (for gems)

    if core.class_groups.gems.keys[tpl_args.class] then
        m_skill.skill(frame, tpl_args)
    end

    --
    -- Handle local stats increases/reductions/additions
    --

    local skip = {}
    -- override attributes for tabula rasa
    if tpl_args._stats.local_unique_tabula_rasa_no_requirement_or_energy_shield ~= nil and tpl_args._stats.local_unique_tabula_rasa_no_requirement_or_energy_shield.min == 1 then
        tpl_args.required_level_final = 1
    end

    -- general stats
    for k, data in pairs(core.stat_map) do
        local value = tpl_args[k]

        if value == nil and data.default ~= nil then
            value = data.default
            tpl_args[k] = data.default
        end

        if value ~= nil and skip[k] == nil then
            value = {min=value, max=value, base=value}
            -- If stats are overriden we scan save some CPU time here
            local overridden = false
            if data.stats_override ~= nil then
                for stat_id, override_value in pairs(data.stats_override) do
                    local stat_value = tpl_args._stats[stat_id]
                    if stat_value ~= nil then
                        -- Use the value of stat
                        if override_value == true then
                            value.min = stat_value.min
                            value.max = stat_value.max
                            overridden = true
                        elseif stat_value ~= 0 then
                            value.min = override_value.min
                            value.max = override_value.max
                            overridden = true
                        end
                    end
                end
           end

           if overridden == false then
                -- The simple cases; this must be using ipairs as "add" must apply before
                for _, operator in ipairs({'add', 'more'}) do
                    local st = data['stats_' .. operator]
                    if st ~= nil then
                        for _, statid in ipairs(st) do
                            if tpl_args._stats[statid] ~= nil then
                                h.stat[operator](value, tpl_args._stats[statid])
                            end
                        end
                    end
                end

                -- For increased stats we need to add them up first
                for stat_key, stat_func in pairs({increased=h.stat.more, increased_inverse=h.stat.more_inverse}) do
                    local st = data['stats_' .. stat_key]
                    if st ~= nil then
                        local total_increase = {min=0, max=0}
                        for _, statid in ipairs(st) do
                            if tpl_args._stats[statid] ~= nil then
                                for var, current_value in pairs(total_increase) do
                                    total_increase[var] = current_value + tpl_args._stats[statid][var]
                                end
                            end
                        end
                        stat_func(value, total_increase)
                    end
                end

                if data.minimum ~= nil then
                    for _, key in ipairs({'min', 'max'}) do
                        if value[key] < data.minimum then
                            value[key] = data.minimum
                        end
                    end
                end
            else

            end

            value.avg = (value.min + value.max) / 2

            -- don't add the properties unless we need to
            if (data.default ~= nil and (value.min ~= data.default or value.max ~= data.default)) or data.default == nil then
                for short_key, range_data in pairs(h.range_map) do
                    tpl_args._properties[data.property .. range_data.property] = value[short_key]
                end

                -- process to HTML to use on list pages or other purposes
                h.handle_range_args(tpl_args, frame, k, data.property, value, data.html_fmt_options or {})
            end

            for short_key, range_data in pairs(h.range_map) do
                tpl_args[k .. range_data.var] = value[short_key]
            end
        end
    end

    -- calculate and handle weapon dps
    if core.class_groups.weapons.keys[tpl_args.class] then
        for _, data in ipairs(core.dps_map) do
            local damage = {
                min = {},
                max = {},
            }

            for var_type, value in pairs(damage) do
                -- covers the min/max/avg range
                for short_key, range_data in pairs(h.range_map) do
                    value[short_key] = 0
                    for _, damage_key in ipairs(data.damage_args) do
                        value[short_key] = value[short_key] + (tpl_args[string.format('%s_%s%s', damage_key, var_type, range_data.var)] or 0)
                    end
                end
            end

            local value = {}
            for short_key, range_data in pairs(h.range_map) do
                local result = (damage.min[short_key] + damage.max[short_key]) / 2 * tpl_args[string.format('attack_speed%s', range_data.var)]
                value[short_key] = result
                tpl_args[string.format('%s%s', data.name, range_data.var)] = result
                -- It will set the property, even if 0.
                -- Not sure if it is better to not set it, but on the other hand this way it can be queried for having no dps of a particular type
                tpl_args._properties[string.format('Has %s%s', data.property, range_data.property)] = result
            end

            if value.avg > 0 then
                h.handle_range_args(tpl_args, frame, data.name, 'Has ' .. data.property, value, data.html_fmt_options or {})
            end
        end
    end

    -- late processing
    core.process_arguments(tpl_args, frame, {array=core.late_args})
    core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].late_args})

    -- Setting semantic properties Part 1 (base values)

    local val

    for _, k in ipairs(tpl_args._total_args) do
        local prop = core.map[k].property
        val = tpl_args[k]
        if val == nil then
        elseif prop == nil then
            --mw.logObject(k)
        else
            tpl_args._properties[prop] = val
        end
    end

    util.smw.set(frame, tpl_args._properties)

    -- Subobjects
    local command
    for key, properties in pairs(tpl_args._subobjects) do
        command = ''
        if type(key) ~= 'number' then
            command = key
        end
        util.smw.subobject(frame, command, properties)
    end

    -- ------------------------------------------------------------------------
    -- Infobox handling
    -- ------------------------------------------------------------------------

    tpl_args._properties = {}
    local container = mw.html.create('span')
        :attr( 'class', 'item-box -' .. tpl_args.frame_type)

    if tpl_args.class == 'Divination Card' then
        --TODO div card code
        container
            :tag('span')
                :attr( 'class', 'divicard-wrapper')
                :tag('span')
                    :attr('class', 'divicard-art')
                    :wikitext( '[[File:' .. tpl_args.card_art .. '|link=|alt=]]' )
                    :done()
                :tag('span')
                    :attr('class', 'divicard-frame')
                    :wikitext( '[[File:Divination card frame.png|link=|alt=]]' )
                    :done()
                :tag('span')
                    :attr('class', 'divicard-header')
                    :wikitext(tpl_args.name)
                    :done()
                :tag('span')
                    :attr('class', 'divicard-stack')
                    :wikitext(tpl_args.stack_size)
                    :done()
                :tag('span')
                    :attr('class', 'divicard-reward')
                    :tag('span')
                        :wikitext(tpl_args.card_reward)
                        :done()
                    :done()
                :tag('span')
                    :attr('class', 'divicard-flavour text-color -flavour')
                    :tag('span')
                        :wikitext(tpl_args.flavour_text)
                        :done()
                    :done()
                :done()
        --TODO Extras?
    else
        local header_css
        if tpl_args.base_item and tpl_args.rarity ~= 'Normal' then
            line_type = 'double'
        else
            line_type = 'single'
        end

        local name_line = tpl_args.name
        if tpl_args.base_item and tpl_args.base_item ~= 'Prophecy' then
            name_line = name_line .. '<br>' .. tpl_args.base_item
        end

        container
            :tag('span')
                :attr( 'class', 'header -' .. line_type )
				:wikitext( name_line )
            :done()

        local grpcont
        local valid
        local statcont = container:tag('span')
        statcont
            :attr('class', 'item-stats')
            :done()

        for _, group in ipairs(core.display_groups) do
            grpcont = {}
            if group.func == nil then
                for _, disp in ipairs(group) do
                    valid = true
                    -- No args to verify which means always valid
                    if disp.args == nil then
                    elseif type(disp.args) == 'table' then
                        for _, key in ipairs(disp.args) do
                            if tpl_args[key] == nil then
                                valid = false
                                break
                            end
                        end
                    elseif type(disp.args) == 'function' then
                        valid = disp.args(tpl_args, frame)
                    end
                    if valid then
                        grpcont[#grpcont+1] = disp.func(tpl_args, frame)
                    end
                end
            else
                grpcont = group.func(tpl_args, frame)
            end

            if #grpcont > 0 then
                statcont
                    :tag('span')
                    :attr('class', 'group ' .. (group.css_class or ''))
                    :wikitext(table.concat(grpcont, '<br>'))
                    :done()
            end
        end
    end

    frame:callParserFunction('#set:', tpl_args._properties)

    if tpl_args.gem_icon ~= nil then
        container:wikitext(string.format('[[%s]]', tpl_args.gem_icon))
    end

    -- Store the infobox so it can be accessed with ease on other pages
    frame:callParserFunction('#set:', {['Has infobox HTML'] = tostring(container)})

    if tpl_args.inventory_icon ~= nil then
        container:wikitext(string.format('[[%s]]', tpl_args.inventory_icon))
    end

    local infobox = mw.html.create('span')
    infobox
        :attr('class', 'infobox-page-container')
        :node(container)

    if tpl_args.skill_screenshot then
        infobox:wikitext(string.format('<br>[[%s|300px]]', tpl_args.skill_screenshot))
    end

    local out = tostring(infobox)

    -- ------------------------------------------------------------------------
    -- Category handling
    -- ------------------------------------------------------------------------

    local cats = {}
    if tpl_args.rarity == 'Unique' then
        cats[#cats+1] = 'Unique ' .. tpl_args.class
    elseif tpl_args.base_item == 'Prophecy' then
        cats[#cats+1] = 'Prophecies'
    else
        cats[#cats+1] = tpl_args.class
    end

    for _, attr in ipairs(m_game.constants.attributes) do
        if tpl_args[attr.long_lower .. '_percent'] then
            cats[#cats+1] = string.format('%s %s', attr.long_upper, tpl_args.class)
        end
    end

    local suffix
    if tpl_args.class == 'Active Skill Gems' or tpl_args.class == 'Support Skill Gems' then
        suffix = ' (gem tag)'
    end
    if suffix ~= nil then
        for _, tag in ipairs(tpl_args.gem_tags) do
            cats[#cats+1] = tag .. suffix
        end
    end

    if #tpl_args.alternate_art_inventory_icons > 0 then
        cats[#cats+1] = 'Items with alternate artwork'
    end

    -- TODO: add maintenance categories

    if tpl_args.release_version == nil then
        cats[#cats+1] = 'Items without a release version'
    end

    if tpl_args._flags.text_modifier then
        cats[#cats+1] = 'Items with improper modifiers'
    end

    out = out .. util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})

    --
    --
    --

    mw.logObject(os.clock() - t)

    return out
end

--
-- Template:Item link & Template:Sl
--

function p.item_link (frame)
    --
    -- Args/Frame
    --

    local tpl_args = getArgs(frame, {
        parentFirst = true,
        removeBlanks = false,
    })
    frame = util.misc.get_frame(frame)

    -- Backwards compability
    tpl_args.item_name = tpl_args.item_name or tpl_args[1]
    if tpl_args.item_name ~= nil then
        tpl_args.item_name = mw.ustring.lower(tpl_args.item_name)
    end
    tpl_args.name = tpl_args.name or tpl_args[2]

    if util.table.has_all_value(tpl_args, {'page', 'item_name', 'item_name_exact'}) then
        error('page, item_name or item_name_exact must be specified')
    end

    tpl_args.large = util.cast.boolean(tpl_args.large)

    local img
    local result

    if util.table.has_one_value(tpl_args, core.item_link_params, nil) or tpl_args.item_name ~= nil then
        local query = {}

        if tpl_args.page ~= nil then
            -- TODO returns the result even if the + format is specified.
            query[#query+1] = string.format('[[%s]]', tpl_args.page)
        else
            if tpl_args.item_name ~= nil then
                query[#query+1] = string.format('[[Has lowercase names::%s]]', tpl_args.item_name)
            elseif tpl_args.item_name_exact ~= nil then
                query[#query+1] = string.format('[[Has name::%s]]', tpl_args.item_name_exact)
            end

            query[#query] = query[#query] .. ' [[Has inventory icon::+]] [[Has infobox HTML::+]]'

            if tpl_args.link_type == 'skill' then
                query[#query] = query[#query] .. ' [[Concept:Skill gems]]'
            end
        end

        query[#query+1] = '?Has name'
        query[#query+1] = '?Has inventory icon'
        query[#query+1] = '?Has infobox HTML'
        query[#query+1] = '?Has alternate inventory icons'
        query[#query+1] = '?Has inventory width'
        query[#query+1] = '?Has inventory height'

        -- attributes
        result = util.smw.query(query, frame)

        local err
        if #result == 0 then
            err = util.misc.raise_error_or_return{raise_required=true, args=tpl_args, msg=string.format(
                'No results found for search parameter "%s".',
                tpl_args.page or tpl_args.item_name or tpl_args.item_name_exact
            )}
        elseif #result > 1 then
            err = util.misc.raise_error_or_return{raise_required=true, args=tpl_args, msg=string.format(
                'Too many results for search parameter "%s". Consider using page parameter instead.',
                tpl_args.page or tpl_args.item_name or tpl_args.item_name_exact
            )}
        end

        if err ~= nil then
            return err .. core.item_link_broken_cat
        end

        result = result[1]
    else
        result = {tpl_args.page or tpl_args.item_name_exact}
    end

    for _, k in ipairs(core.item_link_params) do
        local prop = core.map[k].property
        if tpl_args[k] ~= nil then
            result[prop] = tpl_args[k]
        end
    end

    if tpl_args.image ~= nil then
        if result['Has alternate inventory icons'] == '' then
            return util.misc.raise_error_or_return{raise_required=true, args=tpl_args, msg=string.format(
                'Image parameter was specified, but there is no alternate art defined on page "%s"',
                result[1]
            ) .. core.item_link_broken_cat}
        end

        result['Has alternate inventory icons'] = util.string.split(result['Has alternate inventory icons'], '<MANY>')

        local index = tonumber(tpl_args.image)
        if index ~= nil then
            img = result['Has alternate inventory icons'][index]
        else
            -- offset 1 is needed
            local suffix = string.len(' inventory icon.png') + 1
            -- add an extra offset by 1 to account for the space
            local prefix = string.len(string.sub(result['Has inventory icon'], 1, -suffix)) + 2

            for _, filename in ipairs(result['Has alternate inventory icons']) do
                if string.sub(filename, prefix, -suffix) == tpl_args.image then
                    img = filename
                    break
                end
            end
        end

        if img == nil then
            return util.misc.raise_error_or_return{raise_required=true, args=tpl_args, msg=string.format(
                'Alternate art with index/name "%s" not found on page "%s"',
                tpl_args.image, result[1]
            ) .. core.item_link_broken_cat}
        end
    elseif result['Has inventory icon'] ~= '' then
        img = result['Has inventory icon']
    end

    -- output

    local container = mw.html.create('span')
    container:attr('class', 'inline-infobox-container')

    if not tpl_args.large then
        container:wikitext(string.format('[[%s]]', img))
    end

    container:wikitext(string.format('[[%s|%s]]', result[1], result['Has name'] or result[1]))

    if result['Has infobox HTML'] ~= '' then
        container
            :tag('span')
                :attr('class', 'inline-infobox-hover')
                :wikitext(result['Has infobox HTML'])
                :wikitext(string.format('[[%s]]', img))
                :done()
    end

    if tpl_args.large then
        local width = tonumber(result['Has inventory width']) or tonumber(tpl_args.width)
        local height = tonumber(result['Has inventory height']) or tonumber(tpl_args.height)

        if width and height then
            img = string.format('[[%s|%sx%spx]]', img, width*c.image_size, height*c.image_size)
        elseif width then
            img = string.format('[[%s|%spx]]', img, width*c.image_size)
        elseif height then
            img = string.format('[[%s|x%spx]]', img, height*c.image_size)
        else
            img = string.format('[[%s]]', img)
        end

        container:wikitext(img)
    end

    return tostring(container)
end

--
-- Template:Il
--

function p.item_link_compability (frame)
    if util.misc.is_frame(frame) then
        frame.args.raise = true
    else
        frame.raise = true
    end

    local result

    local status, err = pcall(function ()
        result = p.item_link(frame)
    end)
    mw.logObject(err)

    if status then
        return result
    elseif string.match(err, ".*Too many results.*") then
        return err .. core.item_link_broken_cat
    else
        if frame[1] == nil then
            if frame.page ~= nil then
                frame[1] = frame.page
            end
        end
        return require('Module:Item').itemLink(frame)
    end
end


-- ----------------------------------------------------------------------------
-- Result formatting templates for SMW queries
-- ----------------------------------------------------------------------------

--
-- Template:
--

function p.simple_item_list_row(frame)
    -- Args
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = util.misc.get_frame(frame)

    --
    local args = util.string.split_args(tpl_args.userparam, {sep=', '})
    tpl_args.userparam = args

    local link = p.item_link{page=tpl_args[1], name=tpl_args['?Has name'], inventory_icon=tpl_args['?Has inventory icon'] or '', html=tpl_args['?Has infobox HTML'] or ''}
    if args.format == nil then
        return '* ' .. link
    elseif args.format == 'none' then
        return link
    elseif args.format == 'li' then
        return string.format('<li>%s</li>', link)
    else
        error(string.format('Unrecognized format parameter "%s"', args.format))
    end
end

-- ----------------------------------------------------------------------------
-- Reponsibile for subtemplates of Template:SMW item table
--

-- =p.item_table{stat_column1_format='%s%%', conditions='[[Has item class::+]] [[Has subobject.Has stat id::~additional_*dexterity*]] [[Has rarity::Unique]]', stat_column1_header='Dexterity', stat_column1_stat_format='add', stat_column1_format='Test %s', stat_column1_stat1_id='additional_dexterity', stat_column1_stat2_id='additional_strength_and_dexterity'}
function p.item_table(frame)
    -- args
    local tpl_args = getArgs(frame, {
            parentFirst = true
        })
    frame = util.misc.get_frame(frame)

    local row_infos = {}
    for _, row_info in ipairs(core.result.generic_item) do
        if row_info.arg == nil or tpl_args[row_info.arg] then
            row_infos[#row_infos+1] = row_info
        end
    end

    -- Parse stat arguments
    local stat_columns = {}
    local query_stats = {}
    local stat_results = {}
    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 = {},
        }

        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,
            properties = {},
            display = function(tr, data, properties)
                if col_info.stat_format == 'separate' then
                    local stat_texts = {}
                    local num_stats = 0
                    local avg = 0
                    for _, stat_info in ipairs(col_info.stats) do
                        num_stats = num_stats + 1
                        -- stat results from outside body
                        local stat = (stat_results[data[1]] or {})[stat_info.id]
                        if stat ~= nil then
                            stat_texts[#stat_texts+1] = h.format_value(tpl_args, frame, stat, {no_color=true})
                            avg = avg + stat.avg
                        end
                    end

                    if num_stats ~= #stat_texts then
                        tr:wikitext(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', avg)
                            :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 = (stat_results[data[1]] 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.avg)
                        :attr('class', 'tc -mod')
                        :wikitext(string.format(col_info.format, h.format_value(tpl_args, frame, total_stat, {no_color=true})))
                 else
                    error(string.format('Unknown format %s for 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 query = {tpl_args.conditions}
    for key, value in pairs(tpl_args) do
        if string.sub(key, 0, 2) == 'q_' then
            query[string.sub(key, 3)] = value
        end
    end

    --Override and set defaults
    query.limit = 1000

    -- Set required fields
    query[#query+1] = '?Has name#'
    query[#query+1] = '?Has iventory icon#'
    query[#query+1] = '?Has infobox HTML#'
    query[#query+1] = '?Has inventory width#'
    query[#query+1] = '?Has inventory height#'
    for _, rowinfo in ipairs(row_infos) do
        if type(rowinfo.properties) == 'function' then
            rowinfo.properties = rowinfo.properties()
        end
        for _, property in ipairs(rowinfo.properties) do
            query[#query+1] = '?' .. property .. '#'
        end
    end
    local results = util.smw.query(query, frame)

    if #results == 0 and tpl_args.default ~= nil then
        return tpl_args.default
    end

    if #stat_columns > 0 then
        local continue = true
        local offset = 0
        while continue do
            query = {string.format('[[-Has subobject::<q>%s</q>]] [[Has stat id::+]]', tpl_args.conditions)}
            query[#query+1] = '?Has average stat value#'
            query[#query+1] = '?Has maximum stat value#'
            query[#query+1] = '?Has minimum stat value#'
            query[#query+1] = '?Has stat id#'
            query.limit = 1000
            query.offset = 0
            local temp = util.smw.query(query, frame)
            for _, row in ipairs(temp) do
                if query_stats[row['Has stat id']] ~= nil then
                    local stat = {
                        min = tonumber(row['Has minimum stat value']),
                        max = tonumber(row['Has maximum stat value']),
                        avg = tonumber(row['Has average stat value']),
                    }

                    local page = util.string.split(row[1], '#')[1]
                    if stat_results[page] == nil then
                        stat_results[page] = {[row['Has stat id']] = stat}
                    else
                        stat_results[page][row['Has stat id']] = stat
                    end
                end
            end

            -- stop iteration if we didn't hit the query limit
            if #temp == 1000 then
                offset = offset + 1000
            else
                continue = false
            end
        end
    end

    local tbl = mw.html.create('table')
    tbl:attr('class', 'wikitable sortable item-table')

    -- Header

    local tr = tbl:tag('tr')
    tr
        :tag('th')
            :wikitext(tpl_args.header or 'Item')
            :done()

    for _, row_info in ipairs(row_infos) do
        tr
            :tag('th')
                :attr('data-sort-type', row_info.sort_type or 'number')
                :wikitext(row_info.header)
                :done()
    end

    for _, row in ipairs(results) do
        tr = tbl:tag('tr')

        local il_args = {
            page=row[1],
            name=row['Has name'],
            inventory_icon=row['Has inventory icon'],
            html=row['Has infobox HTML'],
            width=row['Has inventory width'],
            height=row['Has inventory height'],
        }

        if tpl_args.large then
            il_args.large = tpl_args.large
        end

        tr
            :tag('td')
                :wikitext(p.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 _, property in ipairs(rowinfo.properties) do
                -- this will bet set to an empty value not nil confusingly
                if row[property] == '' then
                    display = false
                    break
                end
            end
            if display then
                rowinfo.display(tr, row, rowinfo.properties)
            else
                tr:wikitext(util.html.td.na())
            end
        end
    end

    return tostring(tbl)
end


item_table_factory = {}
function item_table_factory.intro(args)
    -- args:
    --  data_array
    --  header

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

        --
        tpl_args.userparam = util.string.split_args(tpl_args.userparam, {sep=', '})

        local tr = mw.html.create('tr')
        tr
            :tag('th')
                :wikitext(args.header)
                :done()

        for _, rowinfo in ipairs(args.data_array) do
            if rowinfo.arg == nil or tpl_args.userparam[rowinfo.arg] then
                tr
                    :tag('th')
                        :wikitext(rowinfo.header)
                        :done()
            end
        end

        return '<table class="wikitable sortable item-table">' .. tostring(tr)
    end
end

-- Base test: =p.item_list_row{userparam='weapon', 'Clarity', ['?Has name'] = 'Clarity', ['?Has inventory icon'] = 'Clarity inventory icon.png', ['?Has infobox HTML'] = 'x', ['?Has inventory width'] = '1', ['?Has inventory height'] = '1'}
-- =p.item_list_row{userparam='weapon=1', ['?Has base minimum physical damage'] = '5', ['?Has minimum physical damage range average'] = '5', ['?Has minimum physical damage range minimum'] = '5', ['?Has minimum physical damage range maximum'] = '5', ['?Has base maximum physical damage'] = '6', ['?Has maximum physical damage range average'] = '8', ['?Has maximum physical damage range minimum'] = '7', ['?Has maximum physical damage range maximum'] = '9', 'Clarity', ['?Has name'] = 'Clarity', ['?Has inventory icon'] = 'Clarity inventory icon.png', ['?Has infobox HTML'] = 'x', ['?Has inventory width'] = '1', ['?Has inventory height'] = '1'}

function item_table_factory.row(args)
    -- args:
    --  data_array
    return function (frame)
        -- Args
        local tpl_args = getArgs(frame, {
            parentFirst = true
        })
        frame = util.misc.get_frame(frame)

        --
        tpl_args.userparam = util.string.split_args(tpl_args.userparam, {sep=', '})

        local tr = mw.html.create('tr')

        local il_args = {
            page=tpl_args[1],
            name=tpl_args['?Has name'],
            inventory_icon=tpl_args['?Has inventory icon'],
            html=tpl_args['?Has infobox HTML'],
            width=tpl_args['?Has inventory width'],
            height=tpl_args['?Has inventory height'],
        }
        if tpl_args.userparam.large then
            il_args.large = tpl_args.userparam.large
        end

        tr
            :tag('td')
                :wikitext(p.item_link(il_args))
                :done()

        for _, rowinfo in ipairs(args.data_array) do
            if rowinfo.arg == nil or tpl_args.userparam[rowinfo.arg] then
                local value
                if rowinfo.property ~= nil then
                    value = tpl_args['?' .. rowinfo.property]
                    if rowinfo.cast then
                        value = rowinfo.cast(value)
                    end
                end
                rowinfo.display(tr, value, tpl_args)
            end
        end

        return tostring(tr)
    end
end

-- Template:SMW item table/skill_gem/intro
p.skill_gem_list_intro = item_table_factory.intro{data_array=core.result.skill_gem, header='Skill gem'}
-- Template:SMW item table/skill gem
p.skill_gem_list_row = item_table_factory.row{data_array=core.result.skill_gem}

-- ----------------------------------------------------------------------------
-- Item lists
-- ----------------------------------------------------------------------------

function p.skill_gem_list_by_gem_tag(frame)
    -- Args
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = util.misc.get_frame(frame)

    if tpl_args.class == 'Support Skill Gems' then
    elseif tpl_args.class == 'Active Skill Gems' then
    else
        error('invalid item class')
    end

    local query = {}
    query[#query+1] = string.format('[[Has item class::%s]]', tpl_args.class)
    query[#query+1] = '?Has gem tags'
    query[#query+1] = '?Has name'
    query[#query+1] = '?Has inventory icon'
    --query[#query+1] = '?Has infobox HTML'
    query.limit = 5000
    query.sort = 'Has name'

    local results = util.smw.query(query, frame)

    local tags = {}

    for _, row in ipairs(results) do
        row['Has gem tags'] = util.string.split(row['Has gem tags'], '<MANY>')
        for _, tag in ipairs(row['Has gem tags']) do
            if tags[tag] == nil then
                tags[tag] = {}
            end
            table.insert(tags[tag], row)
        end
    end

    local tags_sorted = {}
    for tag, _ in pairs(tags) do
        table.insert(tags_sorted, tag)
    end
    table.sort(tags_sorted)

    local tbl = mw.html.create('table')
    tbl
        :attr('class', 'wikitable sortable')
        :tag('tr')
            :tag('th')
                :wikitext('Tag')
                :done()
            :tag('th')
                :wikitext('Skills')
                :done()
            :done()

    for _, tag in ipairs(tags_sorted) do
        local rows = tags[tag]
        local tr = tbl:tag('tr')
        tr
            :tag('td')
                :wikitext(tag)

        local td = tr:tag('td')
        for i, row in ipairs(rows) do
            td:wikitext(p.item_link{page=row[1], name=row['Has Name'], inventory_icon=row['Has inventory icon'], html=row['Has infobox HTML'] or ''})
            if i < #rows then
                td:wikitext('<br>')
            end
        end
    end

    return tostring(tbl)
end

-- ----------------------------------------------------------------------------
-- Misc. Item templates
-- ----------------------------------------------------------------------------

--
-- Template:Item class
--

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

	if not doInfoCard then
		doInfoCard = require('Module:Infocard')._main
	end

    core.factory.table_cast('name', {key='full', tbl=m_game.constants.item.class})(tpl_args, frame)

    if tpl_args.name_list ~= nil then
        tpl_args.name_list = util.string.split(tpl_args.name_list, ', ')
    else
        tpl_args.name_list = {}
    end

    --

    local ul = mw.html.create('ul')
    for _, item in ipairs(tpl_args.name_list) do
        ul
            :tag('li')
                :wikitext(item)
                :done()
    end


    -- Output Infocard

    local tplargs = {
        ['header'] = tpl_args.name,
        ['subheader'] = '[[Item class]] ' .. util.html.abbr('(?)', 'Item classes categorize items. Classes are often used to restrict items or skill gems to a specific class or by item filters'),
        [1] = 'Also referred to as:' .. tostring(ul),
    }

    -- cats

    local cats = {
        'Item classes',
        tpl_args.name,
    }

    -- Done

    return doInfoCard(tplargs) .. util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
end

-- ----------------------------------------------------------------------------
-- Debug stuff
-- ----------------------------------------------------------------------------

p.debug = {}
function p.debug.show_range_vars ()
    for _, prop in ipairs({'HTML', 'range text', 'range colour'}) do
        for var, data in pairs(core.stat_map) do
            mw.logObject(string.format('%s %s', data.property, prop))
        end

        for _, data in ipairs(core.dps_map) do
            mw.logObject(string.format('Has %s %s', data.property, prop))
        end
    end
end

function p.debug.generic_item_all()
    keys = {}
    for _, data in ipairs(core.result.generic_item) do
        keys[#keys+1] = string.format("['%s'] = '1'", data.arg)
    end

   return table.concat(keys, ', ')
end

return p