Module:Mod/sandbox: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
>OmegaK2
No edit summary
>Illviljan
mNo edit summary
Line 3: Line 3:
--
--


local util = require('Module:Util')
local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local getArgs = require('Module:Arguments').getArgs
local game = require('Module:Game')
local game = require('Module:Game')
local m_item = require('Module:Item2')
local f_item_link = require('Module:Item link').item_link
 
local cargo = mw.ext.cargo
local cargo = mw.ext.cargo


Line 12: Line 13:


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- Utility / Helper functions
-- Strings
-- ----------------------------------------------------------------------------
-- This section contains strings used by this module.
-- Add new strings here instead of in-code directly, this will help other
-- people to correct spelling mistakes easier and help with translation to
-- other PoE wikis.
 
local i18n = {
    args = {
        --
        -- Mod template
        --
       
        -- main
        id = 'id',
        name = 'name',
        mod_group = 'mod_group',
        mod_type = 'mod_type',
        domain = 'domain',
        generation_type = 'generation_type',
        required_level = 'required_level',
        stat_text = 'stat_text',
        granted_buff_id = 'granted_buff_id',
        granted_buff_value = 'granted_buff_value',
        granted_skill = 'granted_skill',
        tags = 'tags',
       
        -- sell price
        sell_price_prefix = 'sell_price',
        item_name = 'name',
        amount = 'amount',
    },
   
    errors = {
        --
        -- Mod template
        --
        sell_price_duplicate_name = 'Do not specify a sell price item name multiple times. Adjust the amount instead.',
        sell_price_missing_argument = 'Both %s and %s must be specified',
    },
    drop_down_table = {
        collapse_all = 'Collapse all',
        expand_all = 'Expand all',
        table_intro = 'The table below displays the available [[modifiers]] for [[item]]s such as',
        prefix = 'Prefix',
        suffix = 'Suffix',
        corrupted = 'Corrupted'     
    },
}
 
-- ----------------------------------------------------------------------------
-- m_utility / Helper functions
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------


Line 30: Line 83:


function h.validate.number (args)
function h.validate.number (args)
     return function (arg)
     return function (tpl_args, frame, value)
         g_args[arg] = util.cast.number(g_args[arg], args)
         return m_util.cast.number(value, args)
        return g_args[arg]
    end
end
 
 
function h.handle_mapped_property_args (map)
    local properties = {}
   
    for _, data in ipairs(map) do
        if data.func ~= nil then
            data.func(data.name)
        end
       
        if data.property ~= nil then
            properties[data.property] = g_args[data.name]
        end
     end
     end
   
    g_frame:callParserFunction('#set:', properties)
end
end


Line 103: Line 138:
end
end


local db = {}
db.mods = {}


db.mods.mods = {
function h.cargo_query(tpl_args)
     _table = 'mods',
     --[[
     {
     Returns a Cargo query of all the results, even if there are more
        name = 'id',
    results than the maximum query limit. It also adds popular fields.
        cargo_type = 'String',
   
        property = 'Is mod',
    tpl_args should include these keys:
        wikitext = 'Mod Id',
     tpl_args.tables
     },
     tpl_args.fields
     {
    tpl_args.q_*
        name = 'name',
   
        cargo_type = 'String',
    ]]
        property = 'Has name',
      
        wikitext = 'Name',
     local tables = m_util.string.split(tpl_args.tables, ', ')
     },
     local fields = m_util.string.split(tpl_args.fields, ', ')
     {
   
        name = 'mod_group',
     -- Parse query arguments
        cargo_type = 'String',
     local query_limit = 5000
        property = 'Has mod group',
    local query = {
        wikitext = 'Group',
         -- Workaround: Fix duplicates but removes other rows as well.  
    },
         groupBy = tables[1] .. '._pageID',
     {
         limit = query_limit,
        name = 'mod_type',
         offset = 0,
        cargo_type = 'String',
    }
        property = 'Has mod type',
    for key, value in pairs(tpl_args) do
        wikitext = 'Mod type',
         if string.sub(key, 0, 2) == 'q_' then
     },
             query[string.sub(key, 3)] = value
     {
         end
        name = 'domain',
     end
        cargo_type = 'Integer',
   
         property = 'Has mod domain',
     -- Add commonly used fields:
        func = h.validate.number{min=1, max=13},
    fields_base = {
         wikitext = 'Mod domain',
         '_pageNamespace',  
        display = function (value)
         '_pageTitle',
            return game.constants.mod.domains[value]['short_upper'] .. ' (Id: ' .. value .. ')'
         '_ID',
        end,
         '_rowID',
    },
         '_pageID',
    {
         '_pageName'
         name = 'generation_type',
         -- '_value'
         cargo_type = 'Integer',
     }
        property = 'Has mod generation type',
     for _, tbl in ipairs(tables) do
         func = h.validate.number{min=1, max=12},
         for _, fld in ipairs(fields_base) do
        wikitext = 'Generation type',
            fields[#fields+1] = string.format('%s.%s', tbl, fld)
        display = function (value)
         end
             return game.constants.mod.generation_types[value]['short_upper'] .. ' (Id: ' .. value .. ')'
     end
         end,
      
     },
    -- Query cargo table. If there are too many results then repeat,  
     {
    -- offset, query and add the remaining results:
        name = 'required_level',
    results = {}    
         cargo_type = 'Integer',
     repeat
         property = 'Has level requirement',
         local result = mw.ext.cargo.query(
         func = h.validate.number{min=0, max=100},
            table.concat(tables, ', '),
        wikitext = 'Req. level',
            table.concat(fields, ', '),
    },
            query
    {
         )
         name = 'stat_text',
         query.offset = query.offset + #result
         cargo_type = 'Searchtext',
         property = 'Has stat text',
         wikitext = 'Effect',
     },
     {
         name = 'granted_buff_id',
        cargo_type = 'String',
        property = 'Has granted buff id',
         wikitext = 'Granted Buff Id',
     },
     {
        name = 'granted_buff_value',
        cargo_type = 'Integer',
        property = 'Has granted buff value',
        wikitext = 'Granted Buff Value',
    },
     {
         name = 'granted_skill',
        property = 'Has granted skill id',
         cargo_type = 'String',
         wikitext = 'Granted Skill',
    },
}


db.mods.mod_spawn_weights = {
         for _,v in ipairs(result) do
    _table = 'mod_spawn_weights',
            results[#results + 1] = v
    {
         end
        name = 'mod_id',
     until #result < query_limit
        cargo_type = 'String',
      
    },
     return results
    {
end
        name = 'order',
        cargo_type = 'Integer',
    },
    {
        name = 'tag',
        cargo_type = 'String',
    },
    {
        name = 'weight',
        cargo_type = 'Integer',
    },
}
 
db.mods.mod_generation_weights = {
    _table = 'mod_generation_weights',
    {
        name = 'mod_id',
        cargo_type = 'String',
    },
    {
        name = 'order',
        cargo_type = 'Integer',
    },
    {
        name = 'tag',
        cargo_type = 'String',
    },
    {
        name = 'weight',
        cargo_type = 'Integer',
    },
}
 
db.mods.mod_sell_prices = {
    _table = 'mod_sell_prices',
    {
         name = 'mod_id',
        cargo_type = 'String',
    },
    {
        name = 'item_name',
         cargo_type = 'String',
     },
     {
        name = 'amount',
        cargo_type = 'Integer',
     },
}


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
Line 249: Line 211:
--
--


util.cargo = {}
local mod_map = {
function util.cargo.declare (frame, arg_map)
    main = {
    -- Args:
        table = 'mods',
    -- frame - frame needed for parser function call
        order = {'id', 'name', 'mod_group', 'mod_type', 'domain', 'generation_type', 'required_level', 'stat_text', 'granted_buff_id', 'granted_buff_value', 'granted_skill', 'tags'},
    -- arg_map - List of tables
        parse_order = {'id', 'name', 'mod_group', 'mod_type', 'domain', 'generation_type', 'required_level', 'stat_text', 'stat_text_raw', 'granted_buff_id', 'granted_buff_value', 'granted_skill', 'tags'},
    --  
        fields = {
    -- arg_map:
            id = {
    -- name - name of the table
                name = i18n.args.id,
     --  cargo_type - cargo type declaration
                field = i18n.args.id,
     local args = {}
                type = 'String',
    args._table = arg_map._table
                wikitext = 'Mod Id',
    for _, row in ipairs(arg_map) do
            },
        args[row.name] = row.cargo_type
            name = {
     end
                name = i18n.args.name,
   
                field = i18n.args.name,
     frame:callParserFunction('#cargo_declare:', args)
                type = 'String',
                wikitext = 'Name',
            },
            mod_group = {
                name = i18n.args.mod_group,
                field = i18n.args.mod_group,
                type = 'String',
                wikitext = 'Group',
            },
            mod_type = {
                name = i18n.args.mod_type,
                field = i18n.args.mod_type,
                type = 'String',
                wikitext = 'Mod type',
            },
            domain = {
                name = i18n.args.domain,
                field = i18n.args.domain,
                type = 'Integer',
                func = h.validate.number{min=1, max=15},
                wikitext = 'Mod domain',
                display = function (value)
                    return game.constants.mod.domains[value]['short_upper'] .. ' (Id: ' .. value .. ')'
                end,
            },
            generation_type = {
                name = i18n.args.generation_type,
                field = i18n.args.generation_type,
                type = 'Integer',
                func = h.validate.number{min=1, max=12},
                wikitext = 'Generation type',
                display = function (value)
                    return game.constants.mod.generation_types[value]['short_upper'] .. ' (Id: ' .. value .. ')'
                end,
            },
            required_level = {
                name = i18n.args.required_level,
                field = i18n.args.required_level,
                type = 'Integer',
                func = h.validate.number{min=0, max=100},
                wikitext = 'Req. level',
            },
            stat_text = {
                name = i18n.args.stat_text,
                field = i18n.args.stat_text,
                type = 'Text',
                wikitext = 'Effect',
            },
            stat_text_raw = {
                name = nil,
                field = 'stat_text_raw',
                type = 'Text',
                func = function(tpl_args, frame)
                    if tpl_args.stat_text then
                        tpl_args.stat_text_raw = string.gsub(
                            -- [[x]] -> x
                            string.gsub(
                                tpl_args.stat_text, '%[%[([^%]|]+)%]%]', '%1'
                            ),
                            -- [[x|y]] -> y
                            '%[%[[^|]+|([^%]|]+)%]%]', '%1'
                        )
                    end
                    return tpl_args.stat_text_raw
                end
            },
            granted_buff_id = {
                name = i18n.args.granted_buff_id,
                field = i18n.args.granted_buff_id,
                type = 'String',
                wikitext = 'Granted Buff Id',
            },
            granted_buff_value = {
                name = i18n.args.granted_buff_value,
                field = i18n.args.granted_buff_value,
                type = 'Integer',
                wikitext = 'Granted Buff Value',
            },
            granted_skill = {
                name = i18n.args.granted_skill,
                field = i18n.args.granted_skill,
                type = 'String',
                wikitext = 'Granted Skill',
            },
            tags = {
                name = i18n.args.tags,
                field = i18n.args.tags,
                type = 'List (,) of String',
                wikitext = 'Tags',
                func = function (tpl_args, frame, value)
                    if value == nil then
                        return {}
                    else
                        return m_util.string.split(value, ', ')
                    end
                end,
                func_smw = function(tpl_args, frame)
                    return table.concat(tpl_args.tags, ';')  
                end,
                func_cargo = function(tpl_args, frame)
                    return table.concat(tpl_args.tags, ',')  
                end,
                display = function(value)
                    return table.concat(value, ', ') 
                end,               
            },
        },
     },
     mod_sell_prices = {
        table = 'mod_sell_prices',
        order = {'name', 'amount'},
        fields = {
            name = {
                name = i18n.args.item_name,
                field = i18n.args.item_name,
                type = 'String',
                func = function (value) return value end,
            },
            amount = {
                name = i18n.args.amount,
                field = i18n.args.amount,
                type = 'Integer',
                func = tonumber,
            },
        },
     },
}
 
p.table_main = m_util.cargo.declare_factory{data=mod_map.main}
p.table_mod_sell_prices = m_util.cargo.declare_factory{data=mod_map.mod_sell_prices}
 
function p.table_mod_stats(frame)
     m_util.cargo.declare(frame, {
        _table = 'mod_stats',
        id = 'String',
        min = 'Integer',
        max = 'Integer',
    })
end
end


for name, db_map in pairs(db.mods) do
    p['_declare_' .. name] = function (frame)
        frame = util.misc.get_frame(frame)
        util.cargo.declare(frame, db_map)
    end
end


-- p.mod{id = "LocalIncreasedPhysicalDamagePercentUniqueOneHandSword2", name = "", mod_group = "LocalPhysicalDamagePercent", domain = "1", generation_type = "3", required_level = "1", mod_type = "LocalPhysicalDamagePercent", stat_text = "150% increased Physical Damage", stat1_id = "local_physical_damage_+%", stat1_min = "150", stat1_max = "150"}
-- p.mod{id = "LocalIncreasedPhysicalDamagePercentUniqueOneHandSword2", name = "", mod_group = "LocalPhysicalDamagePercent", domain = "1", generation_type = "3", required_level = "1", mod_type = "LocalPhysicalDamagePercent", stat_text = "150% increased Physical Damage", stat1_id = "local_physical_damage_+%", stat1_min = "150", stat1_max = "150"}
function p.mod(frame)
function p.mod(frame)
     -- Get args
     -- Get args
     g_args = getArgs(frame, {
     tpl_args = getArgs(frame, {
         parentFirst = true
         parentFirst = true
     })
     })
     g_frame = util.misc.get_frame(frame)
     frame = m_util.misc.get_frame(frame)
      
      
     --
     --
Line 288: Line 381:
     -- Validate single value properties and set them
     -- Validate single value properties and set them
      
      
 
    local cargo_data = {
        _table = mod_map.main.table,
    }
      
      
     h.handle_mapped_property_args(db.mods.mods)
     for _, key in pairs(mod_map.main.parse_order) do
   
        data = mod_map.main.fields[key]
    -- Validate & set multi value property
        local value  
   
        if data.func ~= nil then
    if g_args['tags'] == nil then
            if data.name then
        g_args['tags'] = {}
                value = data.func(tpl_args, frame, tpl_args[data.name])
    else
            else
        local tags = mw.text.split(g_args['tags'], ', ')
                value = data.func(tpl_args, frame)
            end
        else
            value = tpl_args[data.name]
        end
          
          
         g_args['tags'] = tags
         tpl_args[key] = value
   
 
         properties = {}
         if data.field ~= nil then
        properties['Has tag'] = table.concat(tags, ';')
            if data.func_cargo then
        properties['+sep'] = ';'
                cargo_data[data.field] = data.func_cargo(tpl_args, frame)
         util.smw.set(g_frame, properties)
            else
                cargo_data[data.field] = value
            end
         end
     end
     end
    m_util.cargo.store(frame, cargo_data)
      
      
     -- Validate % set the stat subobjects
     -- Validate % set the stat subobjects
     util.args.stats(g_args, {frame=g_frame})
     m_util.args.stats(tpl_args, {frame=frame})
    for _, stat_data in pairs(tpl_args.stats) do
        m_util.cargo.store(frame, {
            _table = 'mod_stats',
            id = stat_data.id,
            min = stat_data.min,
            max = stat_data.max,
        })
    end
      
      
     -- Validate & set spawn weight subobjects
     -- Validate & set spawn weight subobjects
     local i = 0
     m_util.args.spawn_weight_list(tpl_args, {
    local id = nil
         frame=frame,  
    local value = nil
    })
 
    repeat
        i = i + 1
        id = {
            tag = string.format('spawn_weight%s_tag', i),
            value = string.format('spawn_weight%s_value', i),
         }
   
        value = {
            tag = g_args[id.tag],
            value = g_args[id.value],
        }
       
        if value.tag ~= nil and value.value ~= nil then
            properties = {}
            properties['Is tag number'] = i
            properties['Has tag'] = value.tag
            properties['Has spawn weight'] = h.validate.number({min=0})(id.value)
           
            util.smw.subobject(g_frame, string.format('spawn weight %s', i), properties)
           
        elseif not (value.tag == nil and value.value == nil) then
            error ('Both ' .. id.tag .. ' and ' .. id.value .. ' must be specified')
        end
    until value.tag == nil
      
      
     -- Validate & set generation weight subobjects
     -- Validate & set generation weight subobjects
     i = 0
     m_util.args.generation_weight_list(tpl_args, {
    id = nil
         frame=frame,  
    value = nil
    })
 
    repeat
        i = i + 1
        id = {
            tag = string.format('generation_weight%s_tag', i),
            value = string.format('generation_weight%s_value', i),
        }
   
         value = {
            tag = g_args[id.tag],
            value = g_args[id.value],
        }
       
        if value.tag ~= nil and value.value ~= nil then
            properties = {}
            properties['Is tag number'] = i
            properties['Has tag'] = value.tag
            properties['Has generation weight'] = h.validate.number({min=0})(id.value)
           
           
            util.smw.subobject(g_frame, string.format('generation weight %s', i), properties)
           
        elseif not (value.tag == nil and value.value == nil) then
            error ('Both ' .. id.tag .. ' and ' .. id.value .. ' must be specified')
        end
    until value.tag == nil
      
      
     -- Validate & set mod sell values
     -- Validate & set mod sell values
Line 377: Line 438:
     repeat  
     repeat  
         i = i + 1
         i = i + 1
         id = {
          
            name = string.format('sell_price%s_name', i),
        local id = {}
             amount = string.format('sell_price%s_amount', i),
        value = {}
        }
        for key, data in pairs(mod_map.mod_sell_prices.fields) do
   
             id[key] = string.format('%s%s_%s', i18n.args.sell_price_prefix, i, data.name)
        value = {
            value[key] = data.func(tpl_args[id[key]])
            name = g_args[id.name],
         end
            amount = tonumber(g_args[id.amount]),
         }
          
          
         if value.name == nil and value.amount == nil then
         if value.name == nil and value.amount == nil then
Line 391: Line 450:
         elseif value.name ~= nil and value.amount ~= nil then
         elseif value.name ~= nil and value.amount ~= nil then
             if names[value.name] then
             if names[value.name] then
                 error('Do not specify a sell price item name multiple times. Instead adjust the amount')
                 error(i18n.errors.sell_price_duplicate_name)
             else
             else
                 names[value.name] = true
                 names[value.name] = true
             end
             end
           
 
             properties = {}
             local cargo_data = {
             properties['Has sell price item name'] = value.name
                _table = mod_map.mod_sell_prices.table,
            properties['Has sell price amount'] = value.amount
            }
              
             for key, data in pairs(mod_map.mod_sell_prices.fields) do
             util.smw.subobject(g_frame, 'sell price ' .. value.name, properties)
                cargo_data[data.field] = value[key]
             end
             m_util.cargo.store(frame, cargo_data)
              
              
             sell_prices[#sell_prices+1] = value
             sell_prices[#sell_prices+1] = value
         else
         else
             error ('Both ' .. id.name .. ' and ' .. id.amount .. ' must be specified')
             error (string.format(i18n.errors.sell_price_missing_arguments, id.name, id.amount))
         end
         end
          
          
Line 423: Line 484:
         :attr('class', 'wikitable')
         :attr('class', 'wikitable')
      
      
     for _, data in ipairs(db.mods.mods) do
     for _, key in ipairs(mod_map.main.order) do
        local data = mod_map.main.fields[key]
         local text
         local text
         if data.display == nil then
         if data.display == nil then
             text = g_args[data.name]
             text = tpl_args[key]
         else
         else
             text = data.display(g_args[data.name])
             text = data.display(tpl_args[key])
         end
         end
          
          
Line 434: Line 496:
             :tag('tr')
             :tag('tr')
                 :tag('th')
                 :tag('th')
                     :wikitext(string.format('[[Property:%s|%s]]', data.property, data.wikitext))
                     :wikitext(data.wikitext)
                     :done()
                     :done()
                 :tag('td')
                 :tag('td')
Line 449: Line 511:
                 :done()
                 :done()
             :tag('td')
             :tag('td')
                 :wikitext(table.concat(g_args['tags'], ', '))
                 :wikitext(table.concat(tpl_args['tags'], ', '))
                 :done()
                 :done()
             :done()
             :done()
Line 481: Line 543:
         :done()
         :done()
          
          
     for i=1, 5 do
     for i=1, #tpl_args.stats do
         local value = {
         local value = {
             id = g_args['stat' .. i .. '_id'],
             id = tpl_args['stat' .. i .. '_id'],
             min = g_args['stat' .. i .. '_min'],
             min = tpl_args['stat' .. i .. '_min'],
             max = g_args['stat' .. i .. '_max'],
             max = tpl_args['stat' .. i .. '_max'],
         }
         }
          
          
Line 537: Line 599:
         i = i + 1
         i = i + 1
         value = {
         value = {
             tag = g_args[string.format('spawn_weight%s_tag', i)],
             tag = tpl_args[string.format('spawn_weight%s_tag', i)],
             value = g_args[string.format('spawn_weight%s_value', i)],
             value = tpl_args[string.format('spawn_weight%s_value', i)],
         }
         }
          
          
Line 587: Line 649:
         i = i + 1
         i = i + 1
         value = {
         value = {
             tag = g_args[string.format('generation_weight%s_tag', i)],
             tag = tpl_args[string.format('generation_weight%s_tag', i)],
             value = g_args[string.format('generation_weight%s_value', i)],
             value = tpl_args[string.format('generation_weight%s_value', i)],
         }
         }
          
          
Line 635: Line 697:
                     :done()
                     :done()
                 :tag('td')
                 :tag('td')
                     :wikitext(m_item.item_link{item_name_exact=value.name})
                     :wikitext(string.format('[[%s]]', value.name))
                     :done()
                     :done()
                 :done()
                 :done()
Line 644: Line 706:
     out = {}
     out = {}
      
      
     if mw.ustring.find(g_args['id'], '_') then
     if mw.ustring.find(tpl_args['id'], '_') then
         out[#out+1] = g_frame:expandTemplate{ title = 'Incorrect title', args = { title=g_args['id'] } } .. '\n\n\n'
         out[#out+1] = frame:expandTemplate{ title = 'Incorrect title', args = { title=tpl_args['id'] } } .. '\n\n\n'
     end
     end
      
      
     if g_args['name'] then
     if tpl_args['name'] then
         out[#out+1] = string.format("'''%s''' is the internal id of modifier '''%s'''.\n", g_args['id'], g_args['name'])
         out[#out+1] = string.format("'''%s''' is the internal id of modifier '''%s'''.\n", tpl_args['id'], tpl_args['name'])
     else
     else
         out[#out+1] = string.format("'''%s''' is the internal id of an unnamed modifier.\n", g_args['id'], g_args['name'])
         out[#out+1] = string.format("'''%s''' is the internal id of an unnamed modifier.\n", tpl_args['id'], tpl_args['name'])
     end
     end
      
      
Line 660: Line 722:
     -- Done -> output
     -- Done -> output
      
      
     return tostring(container) .. util.misc.add_category(cats) .. '\n' .. table.concat(out)  
     return tostring(container) .. m_util.misc.add_category(cats) .. '\n' .. table.concat(out)  
end
end


Line 672: Line 734:
         parentFirst = true
         parentFirst = true
     })
     })
     g_frame = util.misc.get_frame(frame)
     g_frame = m_util.misc.get_frame(frame)
      
      
     g_args.colspan = 4
     g_args.colspan = 4
Line 727: Line 789:
         -- this works because lua only considers nil to be false >_>
         -- this works because lua only considers nil to be false >_>
         while query.offset do
         while query.offset do
             results = util.smw.query(query, g_frame)
             results = m_util.smw.query(query, g_frame)
             query.offset = query.offset + #results
             query.offset = query.offset + #results
             -- terminates the while if enough reuslts have been fetched
             -- terminates the while if enough reuslts have been fetched
Line 765: Line 827:
         end
         end
          
          
         results = util.smw.query(query, g_frame)
         results = m_util.smw.query(query, g_frame)
          
          
         local last = ''
         local last = ''
Line 803: Line 865:
         parentFirst = true
         parentFirst = true
     })
     })
     g_frame = util.misc.get_frame(frame)
     g_frame = m_util.misc.get_frame(frame)
      
      
     --
     --
     local args = util.string.split_args(g_args.userparam, {sep=', '})
     local args = m_util.string.split_args(g_args.userparam, {sep=', '})
     g_args.userparam = args
     g_args.userparam = args
      
      
Line 813: Line 875:
         args.show_tags = true
         args.show_tags = true
     else
     else
         args.show_tags = util.cast.boolean(args.show_tags)
         args.show_tags = m_util.cast.boolean(args.show_tags)
     end
     end
     args.effect_rowid = (tonumber(args.effect_rowid) or 0) + 1
     args.effect_rowid = (tonumber(args.effect_rowid) or 0) + 1
Line 879: Line 941:
         }
         }
          
          
         result = util.smw.query(query, g_frame)
         result = m_util.smw.query(query, g_frame)
          
          
         local stat_map = {
         local stat_map = {
Line 922: Line 984:


         tags = {}
         tags = {}
         result = util.smw.query(query, g_frame)
         result = m_util.smw.query(query, g_frame)
     end
     end
      
      
Line 991: Line 1,053:


function p.item_sell_price(frame)
function p.item_sell_price(frame)
-- Query and sum the vendor prices for an item.  
    -- Query and sum the vendor prices for an item.  
-- Unidentified items won't currently show the correct vendor price. Not sure how that is specified, nor is it used at all.  
    -- Unidentified items won't currently show the correct vendor price. Not sure how that is specified, nor is it used at all.  
-- Expanding {{il}} seems to give a nil /n the first time a new command is run. Doesn't always happen.
    -- Expanding {{il}} seems to give a nil /n the first time a new command is run. Doesn't always happen.
   
-- = p.item_sell_price{page="Voideye"}
    -- = p.item_sell_price{page="Voideye"}
-- = p.item_sell_price{page="Pyre"}
    -- = p.item_sell_price{page="Pyre"}
-- = p.item_sell_price{page="Vessel of Vinktar (Lightning Penetration)"}
    -- = p.item_sell_price{page="Vessel of Vinktar (Lightning Penetration)"}
   
-- Args
    -- Args
local g_args = getArgs(frame, {
    local g_args = getArgs(frame, {
parentFirst = true
        parentFirst = true
})
    })
local g_frame = util.misc.get_frame(frame)
    local g_frame = m_util.misc.get_frame(frame)
 
    -- Only the explicit modifiers are counted when vendors calculates the price.
    local condition = string.format('[[%s]]', g_args['page'])
    local query_item_mods = {
        condition,
        '?Has explicit mod ids',
        '?Has rarity',
    }
    local results_query_item_mods = m_util.smw.query(query_item_mods, g_frame)
    local item_mods = m_util.string.split(results_query_item_mods[1]['Has explicit mod ids'], '<MANY>')   
   
    -- If the item has a Normal rarity then the sell price would be a fixed price.
    if results_query_item_mods[1]['Has rarity'] == 'Normal' then
        local amount_normal = 1
        local currency_normal = 'Scroll Fragment'
        return string.format('%s %s', amount_normal, g_frame:expandTemplate{title = 'il', args = {currency_normal, currency_normal .. 's'}})
       
        -- return string.format('%s %s', amount_normal, currency_normal)
    end
   
    local mods_sell_price = {}
    for _, modid in ipairs(item_mods) do
        local query_mod_page = {
            string.format('[[Is mod::%s]]', modid),
        }
        local mod_page = m_util.smw.query(query_mod_page, g_frame)
       
        local query_mod_sell_price = {
            string.format('[[-Has subobject::%s]]', mod_page[1][1]),
            '?Has sell price amount#',
            '?Has sell price item name',
        }
        local results = m_util.smw.query(query_mod_sell_price, g_frame)
       
        for _, k in ipairs(results) do
            if k['Has sell price amount'] ~= '' then
                if mods_sell_price[k['Has sell price item name']] == nil then
                    mods_sell_price[k['Has sell price item name']] = k['Has sell price amount']
                else
                    mods_sell_price[k['Has sell price item name']] = k['Has sell price amount'] + mods_sell_price[k['Has sell price item name']]
                end
            end
        end
    end
   
    local out = {}
    for currency, amount in pairs(mods_sell_price) do
        out[#out+1] = string.format('%s %s', amount, g_frame:expandTemplate{title = 'il', args = {currency, currency .. 's'}})
    end
   
    return table.concat(out, ', ')
end   
   


-- Only the explicit modifiers are counted when vendors calculates the price.
function p.get_mod_domain(cargo_query)
local condition = string.format('[[%s]]', g_args['page'])
    --[[  
local query_item_mods = {
Gets the mod domain based on the item class.
condition,
    ]]
'?Has explicit mod ids',
'?Has rarity',
}
local results_query_item_mods = util.smw.query(query_item_mods, g_frame)
local item_mods = util.string.split(results_query_item_mods[1]['Has explicit mod ids'], '<MANY>')
-- If the item has a Normal rarity then the sell price would be a fixed price.
if results_query_item_mods[1]['Has rarity'] == 'Normal' then
local amount_normal = 1
local currency_normal = 'Scroll Fragment'
return string.format('%s %s', amount_normal, g_frame:expandTemplate{title = 'il', args = {currency_normal, currency_normal .. 's'}})
-- return string.format('%s %s', amount_normal, currency_normal)
end
local mods_sell_price = {}
for _, modid in ipairs(item_mods) do
local query_mod_page = {
string.format('[[Is mod::%s]]', modid),
}
local mod_page = util.smw.query(query_mod_page, g_frame)
local query_mod_sell_price = {
string.format('[[-Has subobject::%s]]', mod_page[1][1]),
'?Has sell price amount#',
'?Has sell price item name',
}
local results = util.smw.query(query_mod_sell_price, g_frame)
for _, k in ipairs(results) do
if k['Has sell price amount'] ~= '' then
if mods_sell_price[k['Has sell price item name']] == nil then
mods_sell_price[k['Has sell price item name']] = k['Has sell price amount']
else
mods_sell_price[k['Has sell price item name']] = k['Has sell price amount'] + mods_sell_price[k['Has sell price item name']]
end
end
end
end
local out = {}
for currency, amount in pairs(mods_sell_price) do
out[#out+1] = string.format('%s %s', amount, g_frame:expandTemplate{title = 'il', args = {currency, currency .. 's'}})
end
return table.concat(out, ', ')
end
    local out = cargo_query
    local mod_domains = game.constants.mod.domains
   
    for i,_ in ipairs(out) do
        out[i]['items.domain'] = 1
        for j, row in pairs(mod_domains) do
            if out[i]['items.class']:gsub('Map', 'Area'):match(mod_domains[j]['short_upper']) then -- This may need updating if an area item class doesn't have 'Map' in the string, or if the mod domain descriptions doesn't match the item class.
                out[i]['items.domain'] = j
            end
        end
       
        out[i]['items.domain_text'] = mod_domains[out[i]['items.domain']]['short_lower']
    end


function p.find_mod_domain(input)
    return out
-- Find the mod domain based on the item class.
local out = input
local mod_domains = game.constants.mod.domains
for i,_ in ipairs(out) do
out[i]['Has mod domain'] = 1
for j, row in pairs(mod_domains) do
if out[i]['Has item class']:gsub('Map', 'Area'):match(mod_domains[j]['short_upper']) then -- This may need updating if an area item class doesn't have 'Map' in the string, or if the mod domain descriptions doesn't match the item class.
out[i]['Has mod domain'] = j
end
end
out[i]['Has mod domain text'] = mod_domains[out[i]['Has mod domain']]['short_lower']
end
-- out[1]['Has mod domain'] = 1
-- out[1]['Has mod domain text'] = 'item'
return out
end
end


function p.get_item_tags(frame)
function p.get_item_tags(frame)
-- This function queries for the tags for a specific item.
    -- This function queries for the tags of a specific item.
   
-- Args
    -- Args
local g_args = getArgs(frame, {
    local tpl_args = getArgs(frame, {
parentFirst = true
        parentFirst = true
})
    })
local g_frame = util.misc.get_frame(frame)
    local frame = m_util.misc.get_frame(frame)
   
local item_name = g_args[1]
    local item_name = tpl_args[1]
   
local query = {
    -- tpl_args.tables = 'items'
string.format('[[Has name::%s]]', item_name),
    -- tpl_args.fields = 'items.name, items.tags, items.class'
'?Has tags',
    -- tpl_args.q_where = string.format('items.name = "%s"', item_name)
'?Has base strength requirement',
    -- tpl_args.q_groupBy = 'items._pageID'
'?Has base intelligence requirement',
    -- tpl_args.q_orderBy = 'items.name'
'?Has base dexterity requirement',
 
'?Has item class'
    -- -- Query mods with cargo:
}
    -- results = h.cargo_query(tpl_args)
local results = util.smw.query(query, g_frame)
 
 
for i,_ in ipairs(results) do
 
results[i]['Has tags'] = results[i]['Has tags']:gsub('(<MANY>)', ', ') -- Remove unnecessary symbols.
    -- SMW workaround, remove when module:item2 is ready:
    local results = m_util.smw.query(
function table.reverse(a) -- Reverse order. Item tags are often sorted from lowest to highest priority. Therefore reversing order should lead to finding a match faster most of the time.
        {
local res = {}
            string.format('[[Has name::%s]]', item_name),
for i = #a, 1, -1 do
            '?Has tags',
res[#res+1] = a[i]
            '?Has item class'
end
        },
return res
        frame
end
    )
results[i]['Has tags'] = table.concat(table.reverse(util.string.split(results[i]['Has tags'], ', ')), ', ')
    for i,_ in ipairs(results) do
end  
        results[i]['items.tags'] = results[i]['Has tags']:gsub('(<MANY>)', ', ')
        results[i]['items.class'] = results[i]['Has item class']
results = p.find_mod_domain(results)
        results[i]['items.name'] = results[i]['Has name']
    end  
return results
 
    results = p.get_mod_domain(results)
 
    return results
end
end


function p.header(str)
function p.header(str)
-- Replace specific numbers with a generic #.  
    -- This function replace specific numbers with a generic #.  
local s = table.concat(util.string.split(str, '%(%d+%.*%d*%-%d+%.*%d*%)'), '#')
   
s = table.concat(util.string.split(s, '%d+%.*%d*'), '#')
    local s = table.concat(m_util.string.split(str, '%(%d+%.*%d*%-%d+%.*%d*%)'), '#')
s = table.concat(util.string.split(s, '<br>'), ', ')
    s = table.concat(m_util.string.split(s, '%d+%.*%d*'), '#')
    s = table.concat(m_util.string.split(s, '<br>'), ', ')
    
    
   return s
   return s
end
end


function p.get_spawn_chance(frame)
    --[[
    Calculates the spawn chance of a set of mods that all have a
    spawn weight.
    ]]
    -- Args
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    local frame = m_util.misc.get_frame(frame)
   
    local tbl = tpl_args['tbl']
    -- Probabilities affecting the result besides the spawn weight:
    local chance_multiplier = tonumber(tpl_args['chance_multiplier']) or 1
   
    local N = 0
    for i,_ in ipairs(tbl) do
        -- Total number of outcomes.
        N = N + tbl[i]['spawn_weights.weight']
    end
   
    for i,_ in ipairs(tbl) do
        -- Number of ways it can happen:
        local n = tbl[i]['spawn_weights.weight']
        -- Truncated value:
        tbl[i]['spawn_weights.chance'] = string.format("%0.2f%%",
            n/N * chance_multiplier*100
        )
    end
   
    return tbl
end
   
function p.drop_down_table(frame)
function p.drop_down_table(frame)
-- Misses forsaken masters currently.
    --[[
-- Add a proper expand/collapse toggle for the entire header row so it reacts together with mw-collapsible.  
    This function queries mods in concept pages, queries the subobjects
-- Show Mod group in a better way perhaps:  
    in each mod page for the mod tags. Then compares the tags on the
-- Mod group (expanded)
    item with the mod tags. If there's a match and the spawn weighting
--  # to Damage (Collapsed)
    is larger than zero, then that mod is added to a drop down list.
--      3 to Damage
   
--      5 to Damage
    To Do
-- Add a better solution for properties inside subobjects, for extra_properties. Note that properties already queried for should not be added with this solution.
    * Misses forsaken masters currently.
    * Add a proper expand/collapse toggle for the entire header row so  
-- Weapons
        it reacts together with mw-collapsible.  
-- p.drop_down_table{item = 'Rusted Hatchet', header = 'One Handed Axes'}
    * Show Mod group in a better way perhaps:  
-- p.drop_down_table{item = 'Stone Axe', header = 'Two Handed Axes'}
        Mod group (expanded)
            # to Damage (Collapsed)
            3 to Damage
            5 to Damage
    * Add a better solution for properties inside subobjects, for  
        extra_fields. Note that properties already queried for  
        should not be added with this solution.
   
    Examples:
    Weapons
    p.drop_down_table{item = 'Rusted Hatchet', header = 'One Handed Axes'}
    p.drop_down_table{item = 'Stone Axe', header = 'Two Handed Axes'}


-- Accessories
    Accessories
-- p.drop_down_table{item = 'Amber Amulet', header = 'Amulets'}
    p.drop_down_table{item = 'Amber Amulet', header = 'Amulets'}
   
    Jewels
    p.drop_down_table{item = 'Cobalt Jewel', header = 'Jewels'}
   
    Armour
    p.drop_down_table{item = 'Plate Vest', header = 'Armour body armours'}
    p.drop_down_table{item = 'Iron Greaves', header = 'Armour boots'}
    p.drop_down_table{item = 'Iron Gauntlets', header = 'Armour gloves'}
    p.drop_down_table{item = 'Iron Hat', header = 'Armour helmets'}
    p.drop_down_table{item = 'Splintered Tower Shield', header = 'Armour shields'}
   
    p.drop_down_table{
        item = 'Fishing Rod',
        header = 'FISH PLEASE',
        item_tags = 'fishing_rod',
        extra_fields = 'Has spawn weight, Has spawn chance'
    }
   
    = p.drop_down_table{
        item = 'Fishing Rod',
        item_tags = 'axe, one_hand_weapon, onehand, weapon, default'
    }
-- Jewels
    = p.drop_down_table{
-- p.drop_down_table{item = 'Cobalt Jewel', header = 'Jewels'}
        item = 'Vaals Blade',
    }
-- Armour
       
-- p.drop_down_table{item = 'Plate Vest', header = 'Armour body armours'}
    ]]
-- p.drop_down_table{item = 'Iron Greaves', header = 'Armour boots'}
-- p.drop_down_table{item = 'Iron Gauntlets', header = 'Armour gloves'}
-- p.drop_down_table{item = 'Iron Hat', header = 'Armour helmets'}
-- p.drop_down_table{item = 'Splintered Tower Shield', header = 'Armour shields'}




-- Args
    -- Get template args
local g_args = getArgs(frame, {
    local tpl_args = getArgs(frame,{parentFirst = true})
parentFirst = true
    local frame = m_util.misc.get_frame(frame)
})
local g_frame = util.misc.get_frame(frame)
      
      
local get_item_tags = p.get_item_tags{g_args.item}[1]
    local get_item_tags = p.get_item_tags{tpl_args.item}[1]
local item_tags = {}
    local item_tags = {}
if g_args.item_tags ~= nil then
    if tpl_args.item_tags ~= nil then
item_tags = util.string.split(g_args.item_tags, ', ')
        item_tags = m_util.string.split(tpl_args.item_tags, ', ')  
else  
    else  
item_tags = util.string.split(get_item_tags['Has tags'], ', ')
        item_tags = m_util.string.split(get_item_tags['items.tags'], ', ')
end
    end
   
local header = g_args['header']
    local header = tpl_args['header']
if header == nil then
    if header == nil then
header = table.concat(item_tags, ', ')
        header = table.concat(item_tags, ', ')
end
    end
     
-- Conditions
    -- Create drop down lists in these sections and look in these
local conditions = {}
    -- concept pages for the modifiers:
conditions[1] = 'concept' -- Reserve for Concepts.
    local section = {}
conditions[#conditions+1] = string.format('[[Has subobject::<q> [[Has tag::%s]] </q>]]', table.concat(item_tags, ' || '))
    section = {
        [1] = {
-- Fields
            header = i18n.drop_down_table.prefix,
local all_fields = {
            generation_type = 1, -- See Modifier pages what the id corresponds to.
1,
        },
'Is mod',
        [2] = {
'Has name',
            header = i18n.drop_down_table.suffix,
'Has level requirement',
            generation_type = 2,
'Has mod group',
        },
'Has mod type',
        [3] = {
'Has stat text',
            header = i18n.drop_down_table.corrupted,
}  
            generation_type = 5,
            chance_multiplier = 1/4, -- See Vaal orb, for the 4 possible events.
local extra_properties = {}
        },
if g_args.extra_properties ~= nil then
        -- [4] = {
extra_properties = util.string.split(g_args.extra_properties, ', ')
            -- header = 'Forsaken masters',
local a = #all_fields
            -- generation_type = 'master',  
for _,v in ipairs(extra_properties) do
        -- },
table.insert(all_fields, a+1, v)
    }
end
     
end
     
    -- Introductory text:
local fields = {}
    local out = {}
for _,v in ipairs(all_fields) do
    out[#out+1] = string.format('==%s== \n', header)
fields[#fields+1] = string.format('?%s', v)
    out[#out+1] = string.format('<div style="float: right; text-align:center"><div class="mw-collapsible-collapse-all" style="cursor:pointer;">[%s]</div><hr><div class="mw-collapsible-expand-all" style="cursor:pointer;">[%s]</div></div>',
end
    i18n.drop_down_table.collapse_all,
    i18n.drop_down_table.collapse_all
-- Parameters
    )
local parameters = {
    out[#out+1] = string.format('%s %s.<br><br><br>',  
sort = 'Has mod group, Has mod type, Has level requirement',
        i18n.drop_down_table.table_intro,  
-- limit = 1000, -- lets see
        f_item_link{get_item_tags[1]}
offset = 0,
    )
}
   
    local item_mods = {}
    local tableIndex = -1       
    for _, sctn in ipairs(section) do
        local container = mw.html.create('div')
            :attr('style', 'vertical-align:top; display:inline-block;')
      
      
local data = {}
        -- Format the where condition:
data = {
        generation_type = sctn['generation_type']
[1] = {
        local where = {}
header = 'Prefix',
        for _, item_tag in ipairs(item_tags) do
generation_type = 'prefix',
            where[#where+1] = string.format(
condition = string.format('[[Concept:Spawnable named prefix %s mods]]', get_item_tags['Has mod domain text']),
            '(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
},
            item_tag,
[2] = {
            sctn['generation_type'],
header = 'Suffix',
            get_item_tags['items.domain']
generation_type = 'suffix',
        )
condition = string.format('[[Concept:Spawnable named suffix %s mods]]', get_item_tags['Has mod domain text']),
        end
},
       
[3] = {
        tpl_args.tables = 'mods, spawn_weights, mod_stats'
header = 'Corrupted',
        tpl_args.fields = 'mods.name, mods.id, mods.required_level, mods.generation_type, mods.domain, mods.mod_group, mods.mod_type, mods.stat_text, mod_stats.id, spawn_weights.tag, spawn_weights.weight, spawn_weights.ordinal'
generation_type = 'corrupted',
        tpl_args.q_join = 'mods._pageID=spawn_weights._pageID, mods._pageID=mod_stats._pageID'
condition = string.format('[[Concept:Spawnable corrupted %s mods]]', get_item_tags['Has mod domain text']),
        tpl_args.q_where = table.concat(where, ' OR ')
},
        tpl_args.q_groupBy = 'mods._pageID, spawn_weights.tag, spawn_weights.weight'
-- [4] = {
        tpl_args.q_orderBy = 'mods.generation_type, mods.mod_group, mods.mod_type, mods._pageName, mods.required_level, spawn_weights.ordinal'
-- header = 'Forsaken masters',
       
-- generation_type = 'master',
        local extra_fields = {}
-- condition = '[[Concept:AAAAAAAAAAAAAAAAAAAA]]',
        if tpl_args.extra_fields ~= nil then
-- },
            extra_fields = m_util.string.split(tpl_args.extra_fields, ', ')
}
            tpl_args.fields = string.format(
                '%s, %s',
-- Define the output.
                tpl_args.fields,
local out = {}
                table.concat(extra_fields, ', ')
out[#out+1] = string.format('==%s== \n', header)
            )
out[#out+1] = '<div style="float: right; text-align:center"><div class="mw-collapsible-collapse-all" style="cursor:pointer;">[Collapse All]</div><hr><div class="mw-collapsible-expand-all" style="cursor:pointer;">[Expand All]</div></div>'
        end
out[#out+1] = string.format('The table below displays the available [[modifiers]] for [[item]]s such as %s.<br><br><br>', g_frame:expandTemplate{title = 'Item link', args = {get_item_tags[1]}})
       
        -- Query mods:
local results = {}
        results = h.cargo_query(tpl_args)
local item_mods = {}
local tableIndex = -1
for i_type,_ in ipairs(data) do
local generation_type = data[i_type]['generation_type']
conditions[1] = data[i_type]['condition']
        -- Create own list for spawn weights and group by page name:
local query = {}
        local spawn_weights = {}
for _, v in ipairs(conditions) do
results_unique = {}
query[#query+1] = v
local hash = {}
end
        for _,v in ipairs(results) do
for _, v in ipairs(fields) do
            if spawn_weights[v['mods._pageName']] == nil then  
query[#query+1] = v
                spawn_weights[v['mods._pageName']] = {}
end
            end
for k, v in pairs(parameters) do
            local n = #spawn_weights[v['mods._pageName']] or 0
query[k] = v
            spawn_weights[v['mods._pageName']][n+1] = v
end
if results[generation_type] == nil then  
results[generation_type] = {}
end
repeat
local result = util.smw.query(query, g_frame)
query.offset = query.offset + #result -- Possible error source if only one mod is missing.
for _,v in ipairs(result) do
            -- Get a sorted list that only has unique page names:
results[generation_type][#results[generation_type]+1] = v
            if hash[v['mods._pageName']] ~= true then
end
                results_unique[#results_unique+1] = v
         until #result < 1000
                hash[v['mods._pageName']] = true
            end
item_mods[generation_type] = {}
        end
          
local container = mw.html.create('div')
        if #results_unique > 0 then
:attr('style', 'vertical-align:top; display:inline-block;')
            item_mods[generation_type] = {}
                     
if #results[generation_type] > 0 then
            -- Loop through all the modifiers from the concept pages:
            local last
            for _, v in ipairs(results_unique) do 
                local pagename = v['mods._pageName']
               
                -- Loop through all the modifier tags until they match
                -- the item tags:
                local j = 0
                local tag_match_stop
                repeat
                    j = j+1
                    local mod_tag = spawn_weights[pagename][j]['spawn_weights.tag']
                    local mod_tag_weight = tonumber(
                        spawn_weights[pagename][j]['spawn_weights.weight']
                    )


query_mod_tags = {
                    -- Loop through the item tags until it matches the
string.format('[[-Has subobject::<q>%s</q>]]', table.concat(conditions, ' ')),
                    -- mod tag and the mod tag has a value larger than
'?Has tag#',
                    -- zero:
'?Has spawn weight#',
                    local y = 0
'?Is tag number#',
                    local tag_match_add = false
offset = 0,
                    repeat   
}
                        y = y+1
                        tag_match_stop = ((mod_tag == item_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (spawn_weights[pagename][j] == nil)
local results_mod_tags = {}
                        tag_match_add =  (mod_tag == item_tags[y]) and ((mod_tag_weight or -1) > 0)
local mod_data_subobject = {}
                    until tag_match_stop or y == #item_tags
repeat -- Query again and add the remaining results.  
                   
results_mod_tags[#results_mod_tags+1] = util.smw.query(query_mod_tags, g_frame)
                    -- If there's a match then save that mod and other
query_mod_tags.offset = query_mod_tags.offset + #results_mod_tags[#results_mod_tags]
                    -- interesting information:
                    if tag_match_add then
for _,v in ipairs(results_mod_tags[#results_mod_tags]) do
                       
                        -- Assume that the mod is global then go through
                        -- all the stat ids and check if any of the
                        -- stats are local:
                        local mod_scope = 'Global'
                        for _, vv in ipairs(spawn_weights[pagename]) do
                            if vv['mod_stats.id']:find('.*local.*') ~= nil then
                                mod_scope = 'Local'
                            end
                        end
                       
                        -- Save the matching modifier tag and all other
                        -- interesting properties:
                        local a = #item_mods[generation_type]
                        item_mods[generation_type][a+1] = spawn_weights[pagename][j]
                        item_mods[generation_type][a+1]['spawn_weight.idx_match'] = j
                        item_mods[generation_type][a+1]['mods.scope'] = mod_scope       
item_mods[generation_type][a+1]['mods.add'] = tag_match_add   
item_mods[generation_type][a+1]['mods.stop'] = tag_match_stop   
                    end
                until tag_match_stop
            end
           
            -- If the user wants to see the spawn chance then do the
            -- calculations and save that result as well:
            if tpl_args.spawn_chance ~= nil then
                extra_fields[#extra_fields+1] = 'spawn_weights.chance'
                item_mods[generation_type] = p.get_spawn_chance{
                    tbl = item_mods[generation_type],  
                    chance_multiplier = sctn['chance_multiplier']
                }
            end


local pagename = util.string.split(v[1], '#')[1]
            -- Create the drop down table with <table></table>:
if mod_data_subobject[pagename] == nil then
            local headers = container  
mod_data_subobject[pagename] = {}
            headers
end
                :tag('h3')
                    :wikitext(string.format(
local subobjectname = v[1]
                        '%s',  
mod_data_subobject[pagename][subobjectname] = v
                        sctn['header']
end
                        )
until #results_mod_tags[#results_mod_tags] < 1000
                    )
                    :done()
local last
                :done()
for i, v in ipairs(results[generation_type]) do -- Loop through all the results from the smw query.
               
local pagename = results[generation_type][i][1]
            -- Loop through and add all matching mods to the <table>.
            local tbl, last
local j = 0
            for _, rows in ipairs(item_mods[generation_type]) do
local tag_match_stop
           
                -- If the last mod group is different to the current
repeat -- Loop through the mod tags until a match is found.
                -- mod group then assume the mod isn't related and start
j = j+1
                -- a new drop down list:
local subobjectname = string.format('%s#%s_%s', pagename, 'spawn_weight', j)
                if rows['mods.mod_group'] ~= last then  
local mod_tag = mod_data_subobject[pagename][subobjectname]['Has tag']
                   
local mod_tag_weight = tonumber(mod_data_subobject[pagename][subobjectname]['Has spawn weight'])
                    -- Check through all the mods and see if there are
                    -- multiple mod types within the same mod group:
local y = 0
                    local count = {}
local tag_match_add
                    for _, n in ipairs(item_mods[generation_type]) do  
repeat -- Loop through the item tags until it matches the mod tag and the mod tag has a value.
                       
y = y+1
                        -- If the mod has the same mod group, then add
tag_match_stop = ((mod_tag == item_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (mod_data_subobject[pagename][subobjectname] == nil)
                        -- the mod type to the counter. Only unique mod
tag_match_add = (mod_tag == item_tags[y]) and ((mod_tag_weight or -1) > 0)
                        -- types matter so the number is just a dummy
until tag_match_stop or y == #item_tags
                        -- value:
                        if n['mods.mod_group'] == rows['mods.mod_group'] then
if tag_match_add then
                            count[n['mods.mod_type']] = 1  
for q,w in pairs( mod_data_subobject[pagename][subobjectname]) do -- Complement the useful properties for the subobject.
                        end
results[generation_type][i][q] = w
                    end
end
                   
                    -- Calculate how many unique mod types with the
local a = #item_mods[generation_type]
                    -- same mod group there are:
item_mods[generation_type][a+1] = {}
                    number_of_mod_types = 0
for _,property in ipairs(all_fields) do -- Filtered item modifier table:
                    for _ in pairs(count) do  
item_mods[generation_type][a+1][property] = results[generation_type][i][property]
                        number_of_mod_types = number_of_mod_types + 1  
end
                    end
end
                   
until tag_match_stop
                    -- If there are multiple unique mod types with the
end
                    -- same mod group then change the style of the drop
                    -- down list to indicate it:
--
                    if number_of_mod_types > 1 then  
-- Display in table:
                        tbl_caption = string.format(
--
                            '%s',  
                            m_util.html.poe_color(
local headers = container
                                'mod',  
headers
                                'Mod group: ' .. rows['mods.mod_group']
:tag('h3')
                            )
:wikitext(string.format('%s', data[i_type]['header']))
                        )
:done()
                    else
:done()
                        tbl_caption = string.format(
                            '%s (%s)',  
local last = ''
                            m_util.html.poe_color(
for _, mod_properties in ipairs(item_mods[generation_type]) do  
                                'mod',  
if mod_properties['Has mod group'] ~= last then  
                                p.header(rows['mods.stat_text'])
                            ),  
                            rows['mods.scope']
local count = {}
                        )
for _,n in ipairs(item_mods[generation_type]) do -- Check if there are multiple mod types in the same mod group:
                    end
if n['Has mod group'] == mod_properties['Has mod group'] then
                   
count[n['Has mod type']] = 1
                    -- Add class and style to the <table>:
end
                    tableIndex = tableIndex+1
end
                    tbl = container:tag('table')
                    tbl
local number_of_mod_types = 0
                        :attr('class', 'mw-collapsible mw-collapsed')
for _ in pairs(count) do  
                        :attr('style',  
number_of_mod_types = number_of_mod_types + 1  
                            'text-align:left; line-height:1.60em; width:810px;'
end
                        )
                        :tag('th')
if number_of_mod_types > 1 then  
                            :attr('class',  
tbl_caption = string.format('%s',g_frame:expandTemplate{title = 'c', args = {'mod', 'Mod group: ' .. mod_properties['Has mod group']}})
                                string.format(
else
                                    'mw-customtoggle-%s',  
local mod_appl = 'Global' -- Assume the mod is global if local isn't specified in the mod id.
                                    tableIndex
if mod_properties['Is mod']:find('Local') ~= nil then
                                )
mod_appl = 'Local'
                            )
end
                            :attr('style',  
                                'text-align:left; line-height:1.40em; border-bottom:1pt solid dimgrey;'
tbl_caption = string.format('%s (%s)', g_frame:expandTemplate{title = 'c', args = {'mod', p.header(mod_properties['Has stat text'])}}, mod_appl)
                            )
end
                            :attr('colspan', '3' .. #extra_fields)
                            :wikitext(tbl_caption)
tableIndex = tableIndex+1
                            :done()
tbl = container:tag('table')
                        :done()  
tbl
                end
:attr('class', 'mw-collapsible mw-collapsed')
               
:attr('style', 'text-align:left; line-height:1.60em; width:810px;')
                -- If the mod has no name then use the mod id:
:tag('th')
                local mod_name = rows['mods.name']
:attr('class', string.format('mw-customtoggle-%s', tableIndex))
                if  mod_name == '' or mod_name == nil then  
:attr('style', 'text-align:left; line-height:1.40em; border-bottom:1pt solid dimgrey;')
                    mod_name = rows['mods.id']
:attr('colspan', '3' .. #extra_properties)
                end
:wikitext(tbl_caption)
               
:done()
                -- Check if there are any extra properties to show in
:done()
                -- the drop down list and then add a cell for that,
end
                -- add this node at the end of the table row:
                local td = mw.html.create('td')
local mod_name = mod_properties['Has name']
                if extra_fields ~= nil then  
if  mod_name == '' then  
                    for _, extra_field in ipairs(extra_fields) do  
mod_name = mod_properties['Is mod']
                        td
end
                            :attr('width', '*')
                            :wikitext(string.format(
local td = mw.html.create('td')
                                '%s:&nbsp;%s ',  
if extra_properties ~= nil then  
                                extra_field,  
for _, extra_property in ipairs(extra_properties) do  
                                rows[extra_field]
td
                                )
:attr('width', '*')
                            )
:wikitext(string.format('%s:&nbsp;%s ', extra_property, mod_properties[extra_property]))
                            :done()
:done()
                    end
end
                end
end
               
                -- Add a table row with the interesting properties that
tbl
                -- modifier has:
:tag('tr')
                tbl
:attr('class', 'mw-collapsible mw-collapsed')
                    :tag('tr')
:attr('id', string.format('mw-customcollapsible-%s', tableIndex))
                        :attr('class', 'mw-collapsible mw-collapsed')
:tag('td')
                        :attr(
:attr('width', '160')
                            'id',  
:wikitext(string.format('&nbsp;&nbsp;&nbsp;[[%s|%s]]', mod_properties[1], mod_name:gsub('%s', '&nbsp;')))
                            string.format(
:done()
                                'mw-customcollapsible-%s',  
:tag('td')
                                tableIndex
:attr('width', '1')
                            )
:wikitext(string.format('%s&nbsp;%s',game.level_requirement['short_upper']:gsub('%s', '&nbsp;'), mod_properties['Has level requirement']))
                        )
:done()
                        :tag('td')
:tag('td')
                            :attr('width', '160')
:attr('width', '*')
                            :wikitext(
:wikitext(string.format('%s', g_frame:expandTemplate{title = 'c', args = {'mod', mod_properties['Has stat text']:gsub('<br>', ', ')}}))
                                string.format(
:done()
                                    '&nbsp;&nbsp;&nbsp;[[%s|%s]]',  
:node(td)
                                    rows['mods._pageName'],  
:done()
                                    mod_name:gsub('%s', '&nbsp;')
:done()
                                )
                            )
last = mod_properties['Has mod group']
                            :done()
end
                        :tag('td')
end
                            :attr('width', '1')
                            :wikitext(
out[#out+1] = tostring(container)
                                string.format(
                                    '%s&nbsp;%s',
                                    game.level_requirement['short_upper']:gsub('%s', '&nbsp;'),  
                                    rows['mods.required_level']
                                )
                            )
                            :done()      
                        :tag('td')
                            :attr('width', '*')
                            :wikitext(
                                string.format(
                                    '%s',  
                                    m_util.html.poe_color(
                                        'mod',  
                                        rows['mods.stat_text']:gsub('<br>', ', ')
                                    ) 
                                )
                            )
                            :done()
                        :node(td)
                        :done()
                    :done()
               
                -- Save the last mod group for later comparison:
                last = rows['mods.mod_group']
            end
        end
       
        out[#out+1] = tostring(container)
     end
     end
   
return table.concat(out,'')
    return table.concat(out,'')
end
end


return p
return p

Revision as of 00:49, 29 December 2017

This is the module sandbox page for Module:Mod (diff).

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


This module is used on 34,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.

Lua logo

This module depends on the following other modules:

Module for handling for modifiers with Cargo support.

List of currently implemented templates


--
-- Module for mod related templates
--

local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local game = require('Module:Game')
local f_item_link = require('Module:Item link').item_link

local cargo = mw.ext.cargo

local p = {}

-- ----------------------------------------------------------------------------
-- Strings
-- ----------------------------------------------------------------------------
-- This section contains strings used by this module.
-- Add new strings here instead of in-code directly, this will help other
-- people to correct spelling mistakes easier and help with translation to
-- other PoE wikis.

local i18n = {
    args = {
        --
        -- Mod template
        --
        
        -- main 
        id = 'id',
        name = 'name',
        mod_group = 'mod_group',
        mod_type = 'mod_type',
        domain = 'domain',
        generation_type = 'generation_type',
        required_level = 'required_level',
        stat_text = 'stat_text',
        granted_buff_id = 'granted_buff_id',
        granted_buff_value = 'granted_buff_value',
        granted_skill = 'granted_skill',
        tags = 'tags',
        
        -- sell price
        sell_price_prefix = 'sell_price',
        item_name = 'name',
        amount = 'amount',
    },
    
    errors = {
        --
        -- Mod template
        --
        sell_price_duplicate_name = 'Do not specify a sell price item name multiple times. Adjust the amount instead.',
        sell_price_missing_argument = 'Both %s and %s must be specified',
    },
	
    drop_down_table = {
        collapse_all = 'Collapse all',
        expand_all = 'Expand all',
        table_intro = 'The table below displays the available [[modifiers]] for [[item]]s such as',
        prefix = 'Prefix',
        suffix = 'Suffix',
        corrupted = 'Corrupted'      
    },
}

-- ----------------------------------------------------------------------------
-- m_utility / Helper functions
-- ----------------------------------------------------------------------------

local h = {}

-- Validate single value properties and set them

h.validate = {}

function h.validate.not_nil (args)
    return function (arg)
        if g_args[arg] == nil then
            error(string.format('%s must not be nil', arg))
        end
    end
end

function h.validate.number (args)
    return function (tpl_args, frame, value)
        return m_util.cast.number(value, args)
    end
end

function h.create_header(row)
    local stat = mw.html.create('span')
    local text, nsub = mw.ustring.gsub(row['Has stat text'], '%d+', '?')
    stat
        :attr('class', 'mod-table-header-stat')
        :wikitext(text)
        :done()
        
    local mgroup = mw.html.create('span')
    mgroup
        :attr('class', 'mod-table-header-modgroup')
        :wikitext(row['Has mod group'])
        :done()
        
    local tbl = mw.html.create('table')
    tbl
        :attr('class', 'wikitable mw-collapsible mw-collapsed mod-table') 
        :tag('tr')
            :tag('th')
                :attr('class', 'mod-table-header')
                :attr('colspan', g_args.colspan)
                :tag('span')
                    :attr('class', 'mod-table-header-container')
                    :wikitext(tostring(stat) .. tostring(mgroup))
                :done()
            :done()
    return tbl
end

function h.format_mod(tbl, row, tags)
    local tr = tbl:tag('tr')
    tr
        :tag('td')
            :wikitext(string.format('[[%s|%s]]', row[1], row['Has name']))
            :attr('class', 'mod-table-cell-name')
            :done()
        :tag('td')
            :wikitext(row['Has level requirement'])
            :attr('class', 'mod-table-cell-level')
            :done()
        :tag('td')
            :wikitext(row['Has stat text'])
            :attr('class', 'mod-table-cell-stat')
            :done()
        :tag('td')
            :wikitext(table.concat(tags, ', '))
            :attr('class', 'mod-table-cell-tags')
            :done()
end


function h.cargo_query(tpl_args)
    --[[
    Returns a Cargo query of all the results, even if there are more
    results than the maximum query limit. It also adds popular fields. 
    
    tpl_args should include these keys:
    tpl_args.tables
    tpl_args.fields
    tpl_args.q_*
    
    ]]
    
    local tables = m_util.string.split(tpl_args.tables, ', ')
    local fields = m_util.string.split(tpl_args.fields, ', ')
    
    -- Parse query arguments
    local query_limit = 5000
    local query = {
        -- Workaround: Fix duplicates but removes other rows as well. 
        groupBy = tables[1] .. '._pageID',
        limit = query_limit,
        offset = 0,
    }
    for key, value in pairs(tpl_args) do 
        if string.sub(key, 0, 2) == 'q_' then
            query[string.sub(key, 3)] = value
        end
    end
    
    -- Add commonly used fields:
    fields_base = {
        '_pageNamespace', 
        '_pageTitle',
        '_ID',
        '_rowID',
        '_pageID',
        '_pageName'
        -- '_value'
    }
    for _, tbl in ipairs(tables) do
        for _, fld in ipairs(fields_base) do
            fields[#fields+1] = string.format('%s.%s', tbl, fld)
        end
    end
    
    -- Query cargo table. If there are too many results then repeat, 
    -- offset, query and add the remaining results:
    results = {}      
    repeat
        local result = mw.ext.cargo.query(
            table.concat(tables, ', '),
            table.concat(fields, ', '),
            query
        )
        query.offset = query.offset + #result

        for _,v in ipairs(result) do
            results[#results + 1] = v
        end
    until #result < query_limit
    
    return results
end

-- ----------------------------------------------------------------------------
-- Templates
-- ----------------------------------------------------------------------------

--
-- Template: Mod
--

local mod_map = {
    main = {
        table = 'mods',
        order = {'id', 'name', 'mod_group', 'mod_type', 'domain', 'generation_type', 'required_level', 'stat_text', 'granted_buff_id', 'granted_buff_value', 'granted_skill', 'tags'},
        parse_order = {'id', 'name', 'mod_group', 'mod_type', 'domain', 'generation_type', 'required_level', 'stat_text', 'stat_text_raw', 'granted_buff_id', 'granted_buff_value', 'granted_skill', 'tags'},
        fields = {
            id = {
                name = i18n.args.id,
                field = i18n.args.id,
                type = 'String',
                wikitext = 'Mod Id',
            },
            name = {
                name = i18n.args.name,
                field = i18n.args.name,
                type = 'String',
                wikitext = 'Name',
            },
            mod_group = {
                name = i18n.args.mod_group,
                field = i18n.args.mod_group,
                type = 'String',
                wikitext = 'Group',
            },
            mod_type = {
                name = i18n.args.mod_type,
                field = i18n.args.mod_type,
                type = 'String',
                wikitext = 'Mod type',
            },
            domain = {
                name = i18n.args.domain,
                field = i18n.args.domain,
                type = 'Integer',
                func = h.validate.number{min=1, max=15},
                wikitext = 'Mod domain',
                display = function (value)
                    return game.constants.mod.domains[value]['short_upper'] .. ' (Id: ' .. value .. ')'
                end,
            },
            generation_type = {
                name = i18n.args.generation_type,
                field = i18n.args.generation_type,
                type = 'Integer',
                func = h.validate.number{min=1, max=12},
                wikitext = 'Generation type',
                display = function (value)
                    return game.constants.mod.generation_types[value]['short_upper'] .. ' (Id: ' .. value .. ')'
                end,
            },
            required_level = {
                name = i18n.args.required_level,
                field = i18n.args.required_level,
                type = 'Integer',
                func = h.validate.number{min=0, max=100},
                wikitext = 'Req. level',
            },
            stat_text = {
                name = i18n.args.stat_text,
                field = i18n.args.stat_text,
                type = 'Text',
                wikitext = 'Effect',
            },
            stat_text_raw = {
                name = nil,
                field = 'stat_text_raw',
                type = 'Text',
                func = function(tpl_args, frame)
                    if tpl_args.stat_text then
                        tpl_args.stat_text_raw = string.gsub(
                            -- [[x]] -> x
                            string.gsub(
                                tpl_args.stat_text, '%[%[([^%]|]+)%]%]', '%1'
                            ), 
                            -- [[x|y]] -> y
                            '%[%[[^|]+|([^%]|]+)%]%]', '%1'
                        )
                    end
                    return tpl_args.stat_text_raw
                end
            },
            granted_buff_id = {
                name = i18n.args.granted_buff_id,
                field = i18n.args.granted_buff_id,
                type = 'String',
                wikitext = 'Granted Buff Id',
            },
            granted_buff_value = {
                name = i18n.args.granted_buff_value,
                field = i18n.args.granted_buff_value,
                type = 'Integer',
                wikitext = 'Granted Buff Value',
            },
            granted_skill = {
                name = i18n.args.granted_skill,
                field = i18n.args.granted_skill,
                type = 'String',
                wikitext = 'Granted Skill',
            },
            tags = {
                name = i18n.args.tags,
                field = i18n.args.tags,
                type = 'List (,) of String',
                wikitext = 'Tags', 
                func = function (tpl_args, frame, value)
                    if value == nil then
                        return {}
                    else
                        return m_util.string.split(value, ', ')
                    end
                end,
                func_smw = function(tpl_args, frame)
                    return table.concat(tpl_args.tags, ';')  
                end, 
                func_cargo = function(tpl_args, frame)
                    return table.concat(tpl_args.tags, ',')  
                end,
                display = function(value)
                    return table.concat(value, ', ')  
                end,                 
            },
        },
    },
    mod_sell_prices = {
        table = 'mod_sell_prices',
        order = {'name', 'amount'},
        fields = {
            name = {
                name = i18n.args.item_name,
                field = i18n.args.item_name,
                type = 'String',
                func = function (value) return value end,
            },
            amount = {
                name = i18n.args.amount,
                field = i18n.args.amount,
                type = 'Integer',
                func = tonumber,
            },
        },
    },
}

p.table_main = m_util.cargo.declare_factory{data=mod_map.main}
p.table_mod_sell_prices = m_util.cargo.declare_factory{data=mod_map.mod_sell_prices}

function p.table_mod_stats(frame)
    m_util.cargo.declare(frame, {
        _table = 'mod_stats',
        id = 'String',
        min = 'Integer',
        max = 'Integer',
    })
end


-- p.mod{id = "LocalIncreasedPhysicalDamagePercentUniqueOneHandSword2", name = "", mod_group = "LocalPhysicalDamagePercent", domain = "1", generation_type = "3", required_level = "1", mod_type = "LocalPhysicalDamagePercent", stat_text = "150% increased Physical Damage", stat1_id = "local_physical_damage_+%", stat1_min = "150", stat1_max = "150"}
function p.mod(frame)
    -- Get args
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    --
    -- Validation & semantic properties
    --
    
    -- Validate single value properties and set them
    
    local cargo_data = {
        _table = mod_map.main.table,
    }
    
    for _, key in pairs(mod_map.main.parse_order) do
        data = mod_map.main.fields[key]
        local value 
        if data.func ~= nil then
            if data.name then
                value = data.func(tpl_args, frame, tpl_args[data.name])
            else
                value = data.func(tpl_args, frame)
            end
        else
            value = tpl_args[data.name]
        end
        
        tpl_args[key] = value

        if data.field ~= nil then
            if data.func_cargo then
                cargo_data[data.field] = data.func_cargo(tpl_args, frame)
            else
                cargo_data[data.field] = value
            end
        end
    end

    m_util.cargo.store(frame, cargo_data)
    
    -- Validate % set the stat subobjects
    m_util.args.stats(tpl_args, {frame=frame})
    for _, stat_data in pairs(tpl_args.stats) do
        m_util.cargo.store(frame, {
            _table = 'mod_stats', 
            id = stat_data.id,
            min = stat_data.min,
            max = stat_data.max,
        })
    end
    
    -- Validate & set spawn weight subobjects
    m_util.args.spawn_weight_list(tpl_args, {
        frame=frame, 
    })
    
    -- Validate & set generation weight subobjects
    m_util.args.generation_weight_list(tpl_args, {
        frame=frame, 
    })
    
    -- Validate & set mod sell values
    i = 0
    local names = {}
    local sell_prices = {}
    repeat 
        i = i + 1
        
        local id = {}
        value = {}
        for key, data in pairs(mod_map.mod_sell_prices.fields) do
            id[key] = string.format('%s%s_%s', i18n.args.sell_price_prefix, i, data.name)
            value[key] = data.func(tpl_args[id[key]])
        end
        
        if value.name == nil and value.amount == nil then
            value = nil
        elseif value.name ~= nil and value.amount ~= nil then
            if names[value.name] then
                error(i18n.errors.sell_price_duplicate_name)
            else
                names[value.name] = true
            end

            local cargo_data = {
                _table = mod_map.mod_sell_prices.table,
            }
            for key, data in pairs(mod_map.mod_sell_prices.fields) do
                cargo_data[data.field] = value[key]
            end
            m_util.cargo.store(frame, cargo_data)
            
            sell_prices[#sell_prices+1] = value
        else
            error (string.format(i18n.errors.sell_price_missing_arguments, id.name, id.amount))
        end
        
    until value == nil
    
    --
    -- Display
    --
    
    local container = mw.html.create('div')
    container
        :attr('class', 'modbox')
    
    -- core stats
    
    local tbl = container:tag('table')
    tbl
        :attr('class', 'wikitable')
    
    for _, key in ipairs(mod_map.main.order) do
        local data = mod_map.main.fields[key]
        local text
        if data.display == nil then
            text = tpl_args[key]
        else
            text = data.display(tpl_args[key])
        end
        
        tbl
            :tag('tr')
                :tag('th')
                    :wikitext(data.wikitext)
                    :done()
                :tag('td')
                    :wikitext(text)
                    :done()
                :done()
            :done()
    end
    
    tbl
        :tag('tr')
            :tag('th')
                :wikitext('[[Property:Has tag|Tags]]')
                :done()
            :tag('td')
                :wikitext(table.concat(tpl_args['tags'], ', '))
                :done()
            :done()
        :done()
    
    -- stat table
    
    tbl = container:tag('table')
    tbl
        :attr('class', 'wikitable sortable')
        :tag('tr')
            :tag('th')
                :attr('colspan', 4)
                :wikitext('Stats')
                :done()
            :done()
        :tag('tr')
            :tag('th')
                :wikitext('[[Property:Is stat number|#]]')
                :done()
            :tag('th')
                :wikitext('[[Property:Has stat id|Stat Id]]')
                :done()
            :tag('th')
                :wikitext('[[Property:Has minimum stat value|Minimum]]')
                :done()
            :tag('th')
                :wikitext('[[Property:Has maximum stat value|Maximum]]')
                :done()
            :done()
        :done()
        
    for i=1, #tpl_args.stats do
        local value = {
            id = tpl_args['stat' .. i .. '_id'],
            min = tpl_args['stat' .. i .. '_min'],
            max = tpl_args['stat' .. i .. '_max'],
        }
        
        if value.id then
            tbl
                :tag('tr')
                    :tag('td')
                        :wikitext(i)
                        :done()
                    :tag('td')
                        :wikitext(value.id)
                        :done()
                    :tag('td')
                        :wikitext(value.min)
                        :done()
                    :tag('td')
                        :wikitext(value.max)
                        :done()
                    :done()
                :done()
        end
    end
    
    -- spawn weight table
    
    tbl = container:tag('table')
    tbl
        :attr('class', 'wikitable sortable')
        :tag('tr')
            :tag('th')
                :attr('colspan', 3)
                :wikitext('Spawn Weights')
                :done()
            :done()
        :tag('tr')
            :tag('th')
                :wikitext('[[Property:Is tag number|#]]')
                :done()
            :tag('th')
                :wikitext('[[Property:Has tag|Tag]]')
                :done()
            :tag('th')
                :wikitext('[[Property:Has spawn weight|Weight]]')
                :done()
            :done()
        :done()
        
    i = 0
    value = nil
    repeat
        i = i + 1
        value = {
            tag = tpl_args[string.format('spawn_weight%s_tag', i)],
            value = tpl_args[string.format('spawn_weight%s_value', i)],
        }
        
        if value.tag then
            tbl
                :tag('tr')
                    :tag('td')
                        :wikitext(i)
                        :done()
                    :tag('td')
                        :wikitext(value.tag)
                        :done()
                    :tag('td')
                        :wikitext(value.value)
                        :done()
                    :done()
                :done()
        end
    until value.tag == nil
    
    -- generation weight table
    
    tbl = container:tag('table')
    tbl
        :attr('class', 'wikitable sortable')
        :tag('tr')
            :tag('th')
                :attr('colspan', 3)
                :wikitext('Generation Weights')
                :done()
            :done()
        :tag('tr')
            :tag('th')
                :wikitext('[[Property:Is tag number|#]]')
                :done()
            :tag('th')
                :wikitext('[[Property:Has tag|Tag]]')
                :done()
            :tag('th')
                :wikitext('[[Property:Has generation weight|Weight]]')
                :done()
            :done()
        :done()
    
    i = 0
    value = nil
    repeat
        i = i + 1
        value = {
            tag = tpl_args[string.format('generation_weight%s_tag', i)],
            value = tpl_args[string.format('generation_weight%s_value', i)],
        }
        
        if value.tag then
            tbl
                :tag('tr')
                    :tag('td')
                        :wikitext(i)
                        :done()
                    :tag('td')
                        :wikitext(value.tag)
                        :done()
                    :tag('td')
                        :wikitext(value.value)
                        :done()
                    :done()
                :done()
        end
    until value.tag == nil
    
    -- Sell prices
    tbl = container:tag('table')
    tbl
        :attr('class', 'wikitable sortable')
        :tag('tr')
            :tag('th')
                :attr('colspan', 2)
                :wikitext('Modifier sell price')
                :done()
            :done()
        :tag('tr')
            :tag('th')
                :wikitext('[[Property:Has sell price amount|#]]')
                :done()
            :tag('th')
                :wikitext('[[Property:Has sell price item name|Item]]')
                :done()
            :done()
        :done()
    
    for i, value in ipairs(sell_prices) do
        tbl
            :tag('tr')
                :tag('td')
                    :wikitext(value.amount)
                    :done()
                :tag('td')
                    :wikitext(string.format('[[%s]]', value.name))
                    :done()
                :done()
    end
    
    -- Generic messages on the page
    
    out = {}
    
    if mw.ustring.find(tpl_args['id'], '_') then
        out[#out+1] = frame:expandTemplate{ title = 'Incorrect title', args = { title=tpl_args['id'] } } .. '\n\n\n'
    end
    
    if tpl_args['name'] then
        out[#out+1] = string.format("'''%s''' is the internal id of modifier '''%s'''.\n", tpl_args['id'], tpl_args['name'])
    else
        out[#out+1] = string.format("'''%s''' is the internal id of an unnamed modifier.\n", tpl_args['id'], tpl_args['name'])
    end
    
    -- Categories
    
    cats = {'Mods'}
    
    -- Done -> output
    
    return tostring(container) .. m_util.misc.add_category(cats) .. '\n' .. table.concat(out) 
end

--
-- Template: SMW query mods
-- 

function p.query_mods(frame)
    -- Args
    g_args = getArgs(frame, {
        parentFirst = true
    })
    g_frame = m_util.misc.get_frame(frame)
    
    g_args.colspan = 4
    
    local conditions = {}
    conditions[#conditions+1] = 'concept'
    if g_args.tag then
        conditions[#conditions+1] = string.format('[[Has subobject::<q>[[-Has subobject::+]] [[Has spawn weight::>>0]] [[Has tag::%s]]</q>]]', g_args.tag)
    end
    
    g_args.header_level = g_args.header_level or 2
    
    -- Fields
    local fields = {}
    fields[#fields+1] = '?Is mod'
    fields[#fields+1] = '?Has name'
    fields[#fields+1] = '?Has level requirement'
    fields[#fields+1] = '?Has mod group'
    fields[#fields+1] = '?Has stat text'
    -- parameters
    local parameters = {}
    parameters.sort = 'Has mod group, '
    parameters.limit = 1000 -- lets see
    
    local data = {}
    data.header = {
        prefix = 'Prefix',
        suffix = 'Suffix',
    }
    
    local out = {}
    for _, v in ipairs({'prefix', 'suffix'}) do
        out[#out+1] = string.format('<h%i>%s</h%i>', g_args.header_level, data.header[v], g_args.header_level)
        conditions[1] = string.format('[[Concept:Spawnable named %s item mods]]', v)
        
        local query
        local results
        --
        -- Query tags
        --
        query = {}
        query[#query+1] = string.format('[[-Has subobject::<q>%s</q>]]', table.concat(conditions, ' '))
        query[#query+1] = '[[Has tag::+]]'
        query[#query+1] = '[[Has spawn weight::+]]'
        --query[#query+1] = '[[Has spawn weight::>>0]]'
        query[#query+1] = '?Has tag'
        query[#query+1] = '?Has spawn weight#' -- need native number
        query.limit = 1000
        query.offset = 0
        -- Tag order is very important
        query.sort = ', Is tag number'
        
        local tags = {}
        -- this works because lua only considers nil to be false >_>
        while query.offset do
            results = m_util.smw.query(query, g_frame)
            query.offset = query.offset + #results
            -- terminates the while if enough reuslts have been fetched
            if query.offset % 1000 ~= 0 then
                query.offset = nil
            end
            
            for _, row in ipairs(results) do
                local page, _ = string.gsub(row[1], '#_[%x]+', '')
                if tags[page] == nil then
                    tags[page] = {}
                end
                
                local text
                if tonumber(row['Has spawn weight']) > 0 then
                    text = '[[File:Yes.png|yes|link=]]'
                else
                    text = '[[File:No.png|no|link=]]'
                end
                
                tags[page][#tags[page]+1] = string.format('%s %s', row['Has tag'], text)
            end
        end
        
        --
        -- Query mods
        --
        query = {}
        for _, v in ipairs(conditions) do
            query[#query+1] = v
        end
        for _, v in ipairs(fields) do
            query[#query+1] = v
        end
        for k, v in pairs(parameters) do
            query[k] = v
        end
        
        results = m_util.smw.query(query, g_frame)
        
        local last = ''
        local tbl = ''
        for _, row in ipairs(results) do
            local current = string.gsub(row['Is mod'], '%d+.*', '%%d.*')
            if string.match(last, current) then
                h.format_mod(tbl, row, tags[row[1]])
            else
                out[#out+1] = tostring(tbl)
                tbl = h.create_header(row)
                h.format_mod(tbl, row, tags[row[1]])
            end
            last = row['Is mod']
        end
        
        -- add the last table
        out[#out+1] = tostring(tbl)
    end
    
    return table.concat(out, '')
end

--
-- Template: SMW mod table
-- 

-- =p.mod_list{'Strength1', 'shitty name', 'test', 'test2', userparam='extra_rows=2, show_jewels=1'}
-- =p.mod_list{'ColdCritMultiplier', 'shitty name', 'test', 'test2', userparam='extra_rows=2, show_jewels=1'}
-- =p.mod_list{'MapMultipleExilesMap2Tier', 'asdasda', 'Area yields 15% more Items<br>8% increased Rarity of Items found in this Area<br>Area is inhabited by 2 additional Rogue Exiles<br>Extra monsters ignore rarity bias (Hidden)<br>+14% Monster pack size', userparam='extra_rows=1, type=map, effect_rowid=2'}

function p.mod_list(frame)
    local types = {'map', 'jewel'}
    
    -- Args
    g_args = getArgs(frame, {
        parentFirst = true
    })
    g_frame = m_util.misc.get_frame(frame)
    
    --
    local args = m_util.string.split_args(g_args.userparam, {sep=', '})
    g_args.userparam = args
    
    args.extra_rows = (tonumber(args.extra_rows) or 0)
    if args.show_tags == nil then
        args.show_tags = true
    else
        args.show_tags = m_util.cast.boolean(args.show_tags)
    end
    args.effect_rowid = (tonumber(args.effect_rowid) or 0) + 1

    tr = mw.html.create('tr')
    tr
        :tag('td')
            :attr('data-sort-value', g_args[2] or g_args[1])
            :wikitext(string.format('[[%s|%s]]', g_args[1], g_args[2] or g_args[1]))
            :done()
    
    local i = 2
    local row_max = i + args.extra_rows
    local text
    while i < row_max do
        i = i + 1
        text = g_args[i] or ''
        text = table.concat(mw.text.split(text, ';', true), '<br>')
        
        if args.type == 'map' and i == args.effect_rowid then
            text = mw.text.split(text, '<br>', true)
            
            local map = {
                '%d+%% increased Quantity of Items found in this Area',
                '%d+%% increased Rarity of Items found in this Area',
                '%+%d+%% Monster pack size',
            }
            out = {}
            
            local valid
            for k, v in pairs(text) do
                valid = true
                for _, pattern in ipairs(map) do
                    if mw.ustring.find(v, pattern) ~= nil then
                        valid = false
                        break
                    end
                end
                
                if valid then
                    table.insert(out, v)
                end
            end
            
            text = table.concat(out, '<br>')
        end
            
        tr
            :tag('td')
                :wikitext(text)
                :done()
    end
    
    
    local query
    local result
    
    if args.type == 'map' then
        query = {
            string.format('[[-Has subobject::%s]]', g_args[1]),
            '[[Has stat id::+]]',
            '?Has stat id',
            '?Has minimum stat value',
            '?Has maximum stat value',
        }
        
        result = m_util.smw.query(query, g_frame)
        
        local stat_map = {
            ['map_item_drop_quantity_+%'] = {disp=0, sort=0},
            ['map_item_drop_rarity_+%'] = {disp=0, sort=0},
            ['map_pack_size_+%'] = {disp=0, sort=0},
        }
        
        local stat
        for _, row in ipairs(result) do
            stat = stat_map[row['Has stat id']]
            if stat ~= nil then
                stat.sort = (row['Has minimum stat value'] + row['Has maximum stat value']) / 2
                if row['Has minimum stat value'] ~= row['Has minimum stat value'] then
                    stat.disp = string.format('(%s%-%s)', row['Has minimum stat value'], row['Has maximum stat value'])
                else
                    stat.disp = row['Has minimum stat value']
                end
            end
        end
        
        for _, k in ipairs({'map_item_drop_quantity_+%', 'map_item_drop_rarity_+%', 'map_pack_size_+%'}) do
            stat = stat_map[k]
            tr
                :tag('td')
                    :attr('data-sort-value', stat.sort)
                    :wikitext(stat.disp)
                    :done()
                :done()
        end
    end
    
    local tags
    if args.show_tags or args.type == 'jewel' then
        query = {
            string.format('[[-Has subobject::%s]]', g_args[1]),
            '[[Has tag::+]]',
            '?Has tag',
            '?Has spawn weight',
            sort='Is tag number',
        }

        tags = {}
        result = m_util.smw.query(query, g_frame)
    end
    
    if args.type == 'jewel' then
        local jewels = {
            dex=0,
            str=0,
            int=0,
            pris=0,
        }
        
        local cast_tbl = {
            not_dex={'str', 'int'},
            not_int={'str', 'dex'},
            not_str={'int', 'dex'},
            default={'str','int','dex','pris'},
        }
        
        local i = #result
        local row
        local cast
        while i > 0 do
            row = result[i]
            cast = cast_tbl[row['Has tag']]
            if cast ~= nil then
                for _, k in ipairs(cast) do
                    jewels[k] = row['Has spawn weight']
                end
            end
            
            i = i - 1
        end
        
        tr
            :tag('td')
                :attr('class', 'table-cell-dex')
                :wikitext(jewels.dex)
                :done()
            :tag('td')
                :attr('class', 'table-cell-int')
                :wikitext(jewels.int)
                :done()
            :tag('td')
                :attr('class', 'table-cell-str')
                :wikitext(jewels.str)
                :done()
            :tag('td')
                :attr('class', 'table-cell-prismatic')
                :wikitext(jewels.pris)
                :done()
            :done()
    end
    
    if args.show_tags then
        for _, row in ipairs(result) do
            tags[#tags+1] = string.format('%s %s', row['Has tag'], row['Has spawn weight'])
        end
        
        tr
            :tag('td')
                :wikitext(table.concat(tags, '<br>'))
                :done()
            :done()
    end
    
    return tostring(tr)
end

function p.item_sell_price(frame)
    -- Query and sum the vendor prices for an item. 
    -- Unidentified items won't currently show the correct vendor price. Not sure how that is specified, nor is it used at all. 
    -- Expanding {{il}} seems to give a nil /n the first time a new command is run. Doesn't always happen.
    
    -- = p.item_sell_price{page="Voideye"}
    -- = p.item_sell_price{page="Pyre"}
    -- = p.item_sell_price{page="Vessel of Vinktar (Lightning Penetration)"}
    
    -- Args
    local g_args = getArgs(frame, {
        parentFirst = true
    })
    local g_frame = m_util.misc.get_frame(frame)

    -- Only the explicit modifiers are counted when vendors calculates the price.
    local condition = string.format('[[%s]]', g_args['page'])
    local query_item_mods = {
        condition,
        '?Has explicit mod ids',
        '?Has rarity',
    }
    local results_query_item_mods = m_util.smw.query(query_item_mods, g_frame)
    local item_mods = m_util.string.split(results_query_item_mods[1]['Has explicit mod ids'], '<MANY>')    
    
    -- If the item has a Normal rarity then the sell price would be a fixed price.
    if results_query_item_mods[1]['Has rarity'] == 'Normal' then
        local amount_normal = 1
        local currency_normal = 'Scroll Fragment'
        return string.format('%s %s', amount_normal, g_frame:expandTemplate{title = 'il', args = {currency_normal, currency_normal .. 's'}})
        
        -- return string.format('%s %s', amount_normal, currency_normal)
    end 
    
    local mods_sell_price = {}
    for _, modid in ipairs(item_mods) do
        local query_mod_page = {
            string.format('[[Is mod::%s]]', modid),
        }
        local mod_page = m_util.smw.query(query_mod_page, g_frame)
        
        local query_mod_sell_price = {
            string.format('[[-Has subobject::%s]]', mod_page[1][1]),
            '?Has sell price amount#',
            '?Has sell price item name',
        }
        local results = m_util.smw.query(query_mod_sell_price, g_frame)
        
        for _, k in ipairs(results) do 
            if k['Has sell price amount'] ~= '' then
                if mods_sell_price[k['Has sell price item name']] == nil then
                    mods_sell_price[k['Has sell price item name']] = k['Has sell price amount']
                else
                    mods_sell_price[k['Has sell price item name']] = k['Has sell price amount'] + mods_sell_price[k['Has sell price item name']]
                end
            end
        end 
    end
    
    local out = {}
    for currency, amount in pairs(mods_sell_price) do
        out[#out+1] = string.format('%s %s', amount, g_frame:expandTemplate{title = 'il', args = {currency, currency .. 's'}})
    end
    
    return table.concat(out, ', ')
end    
    

function p.get_mod_domain(cargo_query)
    --[[ 
	Gets the mod domain based on the item class.
    ]]
	
    local out = cargo_query
    local mod_domains = game.constants.mod.domains
    
    for i,_ in ipairs(out) do
        out[i]['items.domain'] = 1
        for j, row in pairs(mod_domains) do
            if out[i]['items.class']:gsub('Map', 'Area'):match(mod_domains[j]['short_upper']) then -- This may need updating if an area item class doesn't have 'Map' in the string, or if the mod domain descriptions doesn't match the item class. 
                out[i]['items.domain'] = j
            end
        end
        
        out[i]['items.domain_text'] = mod_domains[out[i]['items.domain']]['short_lower']
    end

    return out
end

function p.get_item_tags(frame)
    -- This function queries for the tags of a specific item.
    
    -- Args
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    local frame = m_util.misc.get_frame(frame)
    
    local item_name = tpl_args[1]
    
    -- tpl_args.tables = 'items'
    -- tpl_args.fields = 'items.name, items.tags, items.class'
    -- tpl_args.q_where = string.format('items.name = "%s"', item_name)
    -- tpl_args.q_groupBy = 'items._pageID'
    -- tpl_args.q_orderBy = 'items.name'

    -- -- Query mods with cargo:
    -- results = h.cargo_query(tpl_args)



    -- SMW workaround, remove when module:item2 is ready:
    local results = m_util.smw.query(
        {
            string.format('[[Has name::%s]]', item_name),
            '?Has tags',
            '?Has item class'
        }, 
        frame
    )
    for i,_ in ipairs(results) do
        results[i]['items.tags'] = results[i]['Has tags']:gsub('(<MANY>)', ', ')
        results[i]['items.class'] = results[i]['Has item class']
        results[i]['items.name'] = results[i]['Has name']
    end 

    results = p.get_mod_domain(results)

    return results
end

function p.header(str)
    -- This function replace specific numbers with a generic #. 
    
    local s = table.concat(m_util.string.split(str, '%(%d+%.*%d*%-%d+%.*%d*%)'), '#')
    s = table.concat(m_util.string.split(s, '%d+%.*%d*'), '#')
    s = table.concat(m_util.string.split(s, '<br>'), ', ')
   
   return s
end

function p.get_spawn_chance(frame)
    --[[
    Calculates the spawn chance of a set of mods that all have a 
    spawn weight.
    ]]
	
    -- Args
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    local frame = m_util.misc.get_frame(frame)
    
    local tbl = tpl_args['tbl']
    -- Probabilities affecting the result besides the spawn weight:
    local chance_multiplier = tonumber(tpl_args['chance_multiplier']) or 1 
    
    local N = 0
    for i,_ in ipairs(tbl) do
        -- Total number of outcomes.
        N = N + tbl[i]['spawn_weights.weight']
    end
    
    for i,_ in ipairs(tbl) do
        -- Number of ways it can happen:
        local n = tbl[i]['spawn_weights.weight'] 

        -- Truncated value:
        tbl[i]['spawn_weights.chance'] = string.format("%0.2f%%", 
            n/N * chance_multiplier*100
        )
    end 
    
    return tbl
end
    
function p.drop_down_table(frame)
    --[[
    This function queries mods in concept pages, queries the subobjects 
    in each mod page for the mod tags. Then compares the tags on the 
    item with the mod tags. If there's a match and the spawn weighting 
    is larger than zero, then that mod is added to a drop down list.
    
    To Do
    * Misses forsaken masters currently.
    * Add a proper expand/collapse toggle for the entire header row so 
        it reacts together with mw-collapsible. 
    * Show Mod group in a better way perhaps: 
        Mod group (expanded)
            # to Damage (Collapsed)
            3 to Damage
            5 to Damage
    * Add a better solution for properties inside subobjects, for 
        extra_fields. Note that properties already queried for 
        should not be added with this solution.
		
    
    Examples: 
    Weapons
    p.drop_down_table{item = 'Rusted Hatchet', header = 'One Handed Axes'}
    p.drop_down_table{item = 'Stone Axe', header = 'Two Handed Axes'}

    Accessories
    p.drop_down_table{item = 'Amber Amulet', header = 'Amulets'}
    
    Jewels
    p.drop_down_table{item = 'Cobalt Jewel', header = 'Jewels'}
    
    Armour
    p.drop_down_table{item = 'Plate Vest', header = 'Armour body armours'}
    p.drop_down_table{item = 'Iron Greaves', header = 'Armour boots'}
    p.drop_down_table{item = 'Iron Gauntlets', header = 'Armour gloves'}
    p.drop_down_table{item = 'Iron Hat', header = 'Armour helmets'}
    p.drop_down_table{item = 'Splintered Tower Shield', header = 'Armour shields'}
    
    p.drop_down_table{
        item = 'Fishing Rod', 
        header = 'FISH PLEASE', 
        item_tags = 'fishing_rod', 
        extra_fields = 'Has spawn weight, Has spawn chance'
    }
    
    = p.drop_down_table{
        item = 'Fishing Rod',
        item_tags = 'axe, one_hand_weapon, onehand, weapon, default'
    }
	
    = p.drop_down_table{
        item = 'Vaals Blade',
    }
        
    ]]


    -- Get template args
    local tpl_args = getArgs(frame,{parentFirst = true})
    local frame = m_util.misc.get_frame(frame)
    
    local get_item_tags = p.get_item_tags{tpl_args.item}[1]
    local item_tags = {}
    if tpl_args.item_tags ~= nil then
        item_tags = m_util.string.split(tpl_args.item_tags, ', ')    
    else 
        item_tags = m_util.string.split(get_item_tags['items.tags'], ', ')
    end
    
    local header = tpl_args['header']
    if header == nil then
        header = table.concat(item_tags, ', ')
    end
       
    -- Create drop down lists in these sections and look in these 
    -- concept pages for the modifiers:
    local section = {}
    section = {
        [1] = {
            header = i18n.drop_down_table.prefix,
            generation_type = 1, -- See Modifier pages what the id corresponds to.
        },
        [2] = {
            header = i18n.drop_down_table.suffix,
            generation_type = 2,
        },
        [3] = {
            header = i18n.drop_down_table.corrupted,
            generation_type = 5,
            chance_multiplier = 1/4, -- See Vaal orb, for the 4 possible events. 
        },
        -- [4] = {
            -- header = 'Forsaken masters',
            -- generation_type = 'master',    
        -- },
    }
      
       
    -- Introductory text:
    local out = {}
    out[#out+1] = string.format('==%s== \n', header)
    out[#out+1] = string.format('<div style="float: right; text-align:center"><div class="mw-collapsible-collapse-all" style="cursor:pointer;">[%s]</div><hr><div class="mw-collapsible-expand-all" style="cursor:pointer;">[%s]</div></div>',
    i18n.drop_down_table.collapse_all,
    i18n.drop_down_table.collapse_all
    )
    out[#out+1] = string.format('%s %s.<br><br><br>', 
        i18n.drop_down_table.table_intro, 
        f_item_link{get_item_tags[1]}
    )
    
    local item_mods = {}
    local tableIndex = -1        
    for _, sctn in ipairs(section) do
        local container = mw.html.create('div')
            :attr('style', 'vertical-align:top; display:inline-block;')
    
        -- Format the where condition:
        generation_type = sctn['generation_type']
        local where = {}
        for _, item_tag in ipairs(item_tags) do 
            where[#where+1] = string.format(
            '(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
            item_tag,
            sctn['generation_type'],
            get_item_tags['items.domain']
        )
        end
        
        tpl_args.tables = 'mods, spawn_weights, mod_stats'
        tpl_args.fields = 'mods.name, mods.id, mods.required_level, mods.generation_type, mods.domain, mods.mod_group, mods.mod_type, mods.stat_text, mod_stats.id, spawn_weights.tag, spawn_weights.weight, spawn_weights.ordinal'
        tpl_args.q_join = 'mods._pageID=spawn_weights._pageID, mods._pageID=mod_stats._pageID'
        tpl_args.q_where = table.concat(where, ' OR ')
        tpl_args.q_groupBy = 'mods._pageID, spawn_weights.tag, spawn_weights.weight'
        tpl_args.q_orderBy = 'mods.generation_type, mods.mod_group, mods.mod_type, mods._pageName, mods.required_level, spawn_weights.ordinal'
        
        local extra_fields = {}
        if tpl_args.extra_fields ~= nil then
            extra_fields = m_util.string.split(tpl_args.extra_fields, ', ')
            tpl_args.fields = string.format(
                '%s, %s', 
                tpl_args.fields, 
                table.concat(extra_fields, ', ')
            )
        end
        
        -- Query mods:
        results = h.cargo_query(tpl_args)
		
        -- Create own list for spawn weights and group by page name:
        local spawn_weights = {}
		results_unique = {}
		local hash = {}
        for _,v in ipairs(results) do
            if spawn_weights[v['mods._pageName']] == nil then 
                spawn_weights[v['mods._pageName']] = {}
            end
            local n = #spawn_weights[v['mods._pageName']] or 0
            spawn_weights[v['mods._pageName']][n+1] = v
			
            -- Get a sorted list that only has unique page names:
            if hash[v['mods._pageName']] ~= true then
                results_unique[#results_unique+1] = v
                hash[v['mods._pageName']] = true
            end
        end
        
        if #results_unique > 0 then 
            item_mods[generation_type] = {}
                      
            -- Loop through all the modifiers from the concept pages:
            local last
            for _, v in ipairs(results_unique) do   
                local pagename = v['mods._pageName']
                
                -- Loop through all the modifier tags until they match 
                -- the item tags:
                local j = 0
                local tag_match_stop
                repeat 
                    j = j+1
                    local mod_tag = spawn_weights[pagename][j]['spawn_weights.tag']
                    local mod_tag_weight = tonumber(
                        spawn_weights[pagename][j]['spawn_weights.weight']
                    )

                    -- Loop through the item tags until it matches the 
                    -- mod tag and the mod tag has a value larger than 
                    -- zero:
                    local y = 0
                    local tag_match_add = false
                    repeat     
                        y = y+1
                        tag_match_stop = ((mod_tag == item_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (spawn_weights[pagename][j] == nil)
                        tag_match_add =   (mod_tag == item_tags[y]) and ((mod_tag_weight or -1) > 0)
                    until tag_match_stop or y == #item_tags
                    
                    -- If there's a match then save that mod and other 
                    -- interesting information:
                    if tag_match_add then 
                        
                        -- Assume that the mod is global then go through 
                        -- all the stat ids and check if any of the 
                        -- stats are local:
                        local mod_scope = 'Global' 
                        for _, vv in ipairs(spawn_weights[pagename]) do 
                            if vv['mod_stats.id']:find('.*local.*') ~= nil then 
                                mod_scope = 'Local'
                            end
                        end 
                        
                        -- Save the matching modifier tag and all other 
                        -- interesting properties:
                        local a = #item_mods[generation_type]
                        item_mods[generation_type][a+1] = spawn_weights[pagename][j]
                        item_mods[generation_type][a+1]['spawn_weight.idx_match'] = j
                        item_mods[generation_type][a+1]['mods.scope'] = mod_scope         
						item_mods[generation_type][a+1]['mods.add'] = tag_match_add     
						item_mods[generation_type][a+1]['mods.stop'] = tag_match_stop     						
                    end
                until tag_match_stop 
            end
            
            -- If the user wants to see the spawn chance then do the 
            -- calculations and save that result as well:
            if tpl_args.spawn_chance ~= nil then 
                extra_fields[#extra_fields+1] = 'spawn_weights.chance'
                item_mods[generation_type] = p.get_spawn_chance{
                    tbl = item_mods[generation_type], 
                    chance_multiplier = sctn['chance_multiplier']
                }
            end

            -- Create the drop down table with <table></table>:
            local headers = container 
            headers
                :tag('h3')
                    :wikitext(string.format(
                        '%s', 
                        sctn['header']
                        )
                    )
                    :done()
                :done()
                
            -- Loop through and add all matching mods to the <table>. 
            local tbl, last
            for _, rows in ipairs(item_mods[generation_type]) do  
            
                -- If the last mod group is different to the current 
                -- mod group then assume the mod isn't related and start 
                -- a new drop down list:
                if rows['mods.mod_group'] ~= last then 
                    
                    -- Check through all the mods and see if there are 
                    -- multiple mod types within the same mod group:
                    local count = {}
                    for _, n in ipairs(item_mods[generation_type]) do 
                        
                        -- If the mod has the same mod group, then add 
                        -- the mod type to the counter. Only unique mod 
                        -- types matter so the number is just a dummy 
                        -- value:
                        if n['mods.mod_group'] == rows['mods.mod_group'] then
                            count[n['mods.mod_type']] = 1 
                        end
                    end
                    
                    -- Calculate how many unique mod types with the 
                    -- same mod group there are:
                     number_of_mod_types = 0
                    for _ in pairs(count) do 
                        number_of_mod_types = number_of_mod_types + 1 
                    end
                    
                    -- If there are multiple unique mod types with the 
                    -- same mod group then change the style of the drop 
                    -- down list to indicate it:
                    if number_of_mod_types > 1 then 
                        tbl_caption = string.format(
                            '%s', 
                            m_util.html.poe_color(
                                'mod', 
                                'Mod group: ' .. rows['mods.mod_group']
                            )
                        )
                    else
                        tbl_caption = string.format(
                            '%s (%s)', 
                            m_util.html.poe_color(
                                'mod', 
                                p.header(rows['mods.stat_text'])
                            ), 
                            rows['mods.scope']
                        )
                    end
                    
                    -- Add class and style to the <table>:
                    tableIndex = tableIndex+1
                    tbl = container:tag('table')
                    tbl
                        :attr('class', 'mw-collapsible mw-collapsed')
                        :attr('style', 
                            'text-align:left; line-height:1.60em; width:810px;'
                        )
                        :tag('th')
                            :attr('class', 
                                string.format(
                                    'mw-customtoggle-%s', 
                                    tableIndex
                                )
                            )
                            :attr('style', 
                                'text-align:left; line-height:1.40em; border-bottom:1pt solid dimgrey;'
                            )
                            :attr('colspan', '3' .. #extra_fields)
                            :wikitext(tbl_caption)
                            :done()
                        :done()    
                end
                
                -- If the mod has no name then use the mod id:
                local mod_name = rows['mods.name']
                if  mod_name == '' or mod_name == nil then 
                    mod_name = rows['mods.id']
                end
                
                -- Check if there are any extra properties to show in 
                -- the drop down list and then add a cell for that, 
                -- add this node at the end of the table row:
                local td = mw.html.create('td')
                if extra_fields ~= nil then 
                    for _, extra_field in ipairs(extra_fields) do 
                        td
                            :attr('width', '*')
                            :wikitext(string.format(
                                '%s:&nbsp;%s ', 
                                extra_field, 
                                rows[extra_field]
                                )
                            )
                            :done()
                    end
                end
                
                -- Add a table row with the interesting properties that 
                -- modifier has:
                tbl
                    :tag('tr')
                        :attr('class', 'mw-collapsible mw-collapsed')
                        :attr(
                            'id', 
                            string.format(
                                'mw-customcollapsible-%s', 
                                tableIndex
                            )
                        )
                        :tag('td')
                            :attr('width', '160')
                            :wikitext(
                                string.format(
                                    '&nbsp;&nbsp;&nbsp;[[%s|%s]]', 
                                    rows['mods._pageName'], 
                                    mod_name:gsub('%s', '&nbsp;')
                                )
                            )
                            :done()
                        :tag('td')
                            :attr('width', '1')
                            :wikitext(
                                string.format(
                                    '%s&nbsp;%s',
                                    game.level_requirement['short_upper']:gsub('%s', '&nbsp;'), 
                                    rows['mods.required_level']
                                )
                            )
                            :done()        
                        :tag('td')
                            :attr('width', '*')
                            :wikitext(
                                string.format(
                                    '%s', 
                                    m_util.html.poe_color(
                                        'mod', 
                                        rows['mods.stat_text']:gsub('<br>', ', ')
                                    )  
                                )
                            )
                            :done()
                        :node(td)
                        :done()
                    :done()
                
                -- Save the last mod group for later comparison:
                last = rows['mods.mod_group']
            end
        end
        
        out[#out+1] = tostring(container)
    end
    
    return table.concat(out,'')
end

return p