Module:Mod

From Path of Exile Wiki
Revision as of 22:34, 19 October 2016 by >Illviljan (Using subobjects to check if a mod is local or global. Which should fix issues with flasks.)
Jump to navigation Jump to search
Module documentation[view] [edit] [history] [purge]


This module is used on 34000+ 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

de:Modul:Mod

--
-- Module for mod related templates
--

local util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local game = require('Module:Game')
local m_item = require('Module:Item2')

local p = {}

-- ----------------------------------------------------------------------------
-- 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 (arg)
        g_args[arg] = util.cast.number(g_args[arg], 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
    
    g_frame:callParserFunction('#set:', properties)
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


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

--
-- Template: Mod
--

-- 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
    g_args = getArgs(frame, {
        parentFirst = true
    })
    g_frame = util.misc.get_frame(frame)
    
    --
    -- Validation & semantic properties
    --
    
    -- Validate single value properties and set them
    
    local map = {
        {
            name = 'id',
            property = 'Is mod',
            wikitext = 'Mod Id',
        },
        {
            name = 'name',
            property = 'Has name',
            wikitext = 'Name',
        },
        {
            name = 'mod_group',
            property = 'Has mod group',
            wikitext = 'Group',
        },
        {
            name = 'mod_type',
            property = 'Has mod type',
            wikitext = 'Mod type',
        },
        {
            name = 'domain',
            property = 'Has mod domain',
            func = h.validate.number{min=1, max=13},
            wikitext = 'Mod domain',
            display = function (value)
                return game.constants.mod.domains[value]['short_upper'] .. ' (Id: ' .. value .. ')'
            end,
        },
        {
            name = 'generation_type',
            property = 'Has mod generation type',
            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,
        },
        {
            name = 'required_level',
            property = 'Has level requirement',
            func = h.validate.number{min=0, max=100},
            wikitext = 'Req. level',
        },
        {
            name = 'stat_text',
            property = 'Has stat text',
            wikitext = 'Effect',
        },
        {
            name = 'granted_buff_id',
            property = 'Has granted buff id',
            wikitext = 'Granted Buff Id',
        },
        {
            name = 'granted_buff_value',
            property = 'Has granted buff value',
            wikitext = 'Granted Buff Value',
        },
        {
            name = 'granted_skill',
            property = 'Has granted skill id',
            wikitext = 'Granted Skill',
        },
    }
    
    h.handle_mapped_property_args(map)
    
    -- Validate & set multi value property
    
    if g_args['tags'] == nil then
        g_args['tags'] = {}
    else
        local tags = mw.text.split(g_args['tags'], ', ')
        
        g_args['tags'] = tags
    
        properties = {}
        properties['Has tag'] = table.concat(tags, ';')
        properties['+sep'] = ';'
        util.smw.set(g_frame, properties)
    end
    
    -- Validate % set the stat subobjects
    util.args.stats(g_args, {frame=g_frame})
    
    -- Validate & set spawn weight subobjects
    local i = 0
    local id = nil
    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
    i = 0
    id = nil
    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
    i = 0
    local names = {}
    local sell_prices = {}
    repeat 
        i = i + 1
        id = {
            name = string.format('sell_price%s_name', i),
            amount = string.format('sell_price%s_amount', i),
        }
    
        value = {
            name = g_args[id.name],
            amount = tonumber(g_args[id.amount]),
        }
        
        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('Do not specify a sell price item name multiple times. Instead adjust the amount')
            else
                names[value.name] = true
            end
            
            properties = {}
            properties['Has sell price item name'] = value.name
            properties['Has sell price amount'] = value.amount
            
            util.smw.subobject(g_frame, 'sell price ' .. value.name, properties)
            
            sell_prices[#sell_prices+1] = value
        else
            error ('Both ' .. id.name .. ' and ' .. id.amount .. ' must be specified')
        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 _, data in ipairs(map) do
        local text
        if data.display == nil then
            text = g_args[data.name]
        else
            text = data.display(g_args[data.name])
        end
        
        tbl
            :tag('tr')
                :tag('th')
                    :wikitext(string.format('[[Property:%s|%s]]', data.property, 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(g_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, 5 do
        local value = {
            id = g_args['stat' .. i .. '_id'],
            min = g_args['stat' .. i .. '_min'],
            max = g_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 = g_args[string.format('spawn_weight%s_tag', i)],
            value = g_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 = g_args[string.format('generation_weight%s_tag', i)],
            value = g_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(m_item.item_link{item_name_exact=value.name})
                    :done()
                :done()
    end
    
    -- Generic messages on the page
    
    out = {}
    
    if mw.ustring.find(g_args['id'], '_') then
        out[#out+1] = g_frame:expandTemplate{ title = 'Incorrect title', args = { title=g_args['id'] } } .. '\n\n\n'
    end
    
    if g_args['name'] then
        out[#out+1] = string.format("'''%s''' is the internal id of modifier '''%s'''.\n", g_args['id'], g_args['name'])
    else
        out[#out+1] = string.format("'''%s''' is the internal id of an unnamed modifier.\n", g_args['id'], g_args['name'])
    end
    
    -- Categories
    
    cats = {'Mods'}
    
    -- Done -> output
    
    return tostring(container) .. 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 = 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 = 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 = 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 = util.misc.get_frame(frame)
    
    --
    local args = 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 = 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 = 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 = 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 = 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 = 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	
	

function p.find_mod_domain(input)
	-- 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

function p.get_item_tags(frame)
	-- This function queries for the tags for a specific item.
	
	-- Args
	local g_args = getArgs(frame, {
		parentFirst = true
	})
	local g_frame = util.misc.get_frame(frame)
	
	local item_name = g_args[1]
	
	local query = {
		string.format('[[Has name::%s]]', item_name),
		'?Has tags',
		'?Has base strength requirement',
		'?Has base intelligence requirement',
		'?Has base dexterity requirement',
		'?Has item class'
	}
	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.
		
		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 = {}
			for i = #a, 1, -1 do
				res[#res+1] = a[i]
			end
			return res
		end
		results[i]['Has tags'] = table.concat(table.reverse(util.string.split(results[i]['Has tags'], ', ')), ', ')
	end 
	
	results = p.find_mod_domain(results)
	
	return results
end

function p.header(str)
	-- 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*'), '#')
	s = table.concat(util.string.split(s, '<br>'), ', ')
   
   return s
end

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. 
	-- 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_properties. Note that properties already queried for should not be added with this solution.
	
	-- 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'}


	-- Args
	local g_args = getArgs(frame, {
		parentFirst = true
	})
	local g_frame = util.misc.get_frame(frame)
    
	local get_item_tags = p.get_item_tags{g_args.item}[1]
	local item_tags = {}
	if g_args.item_tags ~= nil then
		item_tags = util.string.split(g_args.item_tags, ', ')	
	else 
		item_tags = util.string.split(get_item_tags['Has tags'], ', ')
	end
	
	local header = g_args['header']
	if header == nil then
		header = table.concat(item_tags, ', ')
	end
	
	-- Conditions
	local conditions = {}
	conditions[1] = 'concept' -- Reserve for Concepts.
	conditions[#conditions+1] = string.format('[[Has subobject::<q> [[Has tag::%s]] </q>]]', table.concat(item_tags, ' || '))
 
	-- Fields
	local all_fields = {
		1,
		'Is mod',
		'Has name',
		'Has level requirement',
		'Has mod group',
		'Has mod type',
		'Has stat text',
	} 
	
	local extra_properties = {}
	if g_args.extra_properties ~= nil then 
		extra_properties = util.string.split(g_args.extra_properties, ', ')
		local a = #all_fields
		for _,v in ipairs(extra_properties) do 
			table.insert(all_fields, a+1, v)
		end
	end 
	
	local fields = {}
	for _,v in ipairs(all_fields) do
		fields[#fields+1] = string.format('?%s', v)
	end 
	
	-- Parameters
	local parameters = {
		sort = 'Has mod group, Has mod type, Has level requirement',
		-- limit = 1000, -- lets see
		offset = 0,
	}
    
	local data = {}
	data = {
		[1] = {
			header = 'Prefix',
			generation_type = 'prefix',
			condition = string.format('[[Concept:Spawnable named prefix %s mods]]', get_item_tags['Has mod domain text']),
		},
		[2] = {
			header = 'Suffix',
			generation_type = 'suffix',
			condition = string.format('[[Concept:Spawnable named suffix %s mods]]', get_item_tags['Has mod domain text']),
		},
		[3] = {
			header = 'Corrupted',
			generation_type = 'corrupted',
			condition = string.format('[[Concept:Spawnable corrupted %s mods]]', get_item_tags['Has mod domain text']),
		},
		-- [4] = {
			-- header = 'Forsaken masters',
			-- generation_type = 'master',	
			-- condition = '[[Concept:AAAAAAAAAAAAAAAAAAAA]]',
		-- },
	}
	
	-- Define the output.
	local out = {}
	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>'	
	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]}})
	
	local results = {}
	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']
		local 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
		
		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
				results[generation_type][#results[generation_type]+1] = v
			end
        until #result < 1000
		
		item_mods[generation_type] = {}
		
		local container = mw.html.create('div')
			:attr('style', 'vertical-align:top; display:inline-block;')
		
		if #results[generation_type] > 0 then 
			query_mod_tags = {
				string.format('[[-Has subobject::<q>%s</q>]]', table.concat(conditions, ' ')),
				'?Has tag#',
				'?Has spawn weight#',
				'?Is tag number#',
				offset = 0,
			}
			
			local results_mod_tags = {}
			local mod_data_subobject = {}
			repeat -- Query again and add the remaining results. 
				results_mod_tags[#results_mod_tags+1] = util.smw.query(query_mod_tags, g_frame)
				query_mod_tags.offset = query_mod_tags.offset + #results_mod_tags[#results_mod_tags]
				
				for _,v in ipairs(results_mod_tags[#results_mod_tags]) do

					local pagename = util.string.split(v[1], '#')[1]
					if mod_data_subobject[pagename] == nil then 
						mod_data_subobject[pagename] = {}
					end
					
					local subobjectname = v[1]
					mod_data_subobject[pagename][subobjectname] = v
				end
			until #results_mod_tags[#results_mod_tags] < 1000
			
			local last
			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]
				
				local j = 0 
				local tag_match_stop
				repeat -- Loop through the mod tags until a match is found.
					j = j+1
					local subobjectname = string.format('%s#%s_%s', pagename, 'spawn_weight', j)
					local mod_tag = mod_data_subobject[pagename][subobjectname]['Has tag']
					local mod_tag_weight = tonumber(mod_data_subobject[pagename][subobjectname]['Has spawn weight'])
					
					local y = 0
					local tag_match_add
					repeat	 -- Loop through the item tags until it matches the mod tag and the mod tag has a value.
						y = y+1
						tag_match_stop = ((mod_tag == item_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (mod_data_subobject[pagename][subobjectname] == 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 tag_match_add then 
						local mod_scope = 'Global' 
						for subobject, subobject_val in pairs(mod_data_subobject[pagename]) do 
							if subobject:find('stat.*local') ~= nil then -- If at least one stat is local then change the scope of the mod to local.
								mod_scope = 'Local'
							end
							
							if subobject == subobjectname then -- Complement with the properties the matching subobject has.
								for property,w in pairs(subobject_val) do 
									if results[generation_type][i][property] == nil then
										results[generation_type][i][property] = {}
									end
									
									results[generation_type][i][property] = w
								end
							end
						end 
						
						local a = #item_mods[generation_type]
						item_mods[generation_type][a+1] = {}
						for _,property in ipairs(all_fields) do -- Filtered item modifier table:
							item_mods[generation_type][a+1][property] = results[generation_type][i][property]
						end
						item_mods[generation_type][a+1]['Has mod scope'] = mod_scope
					end
				until tag_match_stop 
			end
			
			--
			-- Display in table:
			--
			local headers = container 	
			headers
				:tag('h3')
					:wikitext(string.format('%s', data[i_type]['header']))
					:done()
				:done()
				
			local last = ''
			for _, mod_properties in ipairs(item_mods[generation_type]) do 
				if mod_properties['Has mod group'] ~= last then 

					local count = {}
					for _,n in ipairs(item_mods[generation_type]) do -- Check if there are multiple mod types in the same mod group:
						if n['Has mod group'] == mod_properties['Has mod group'] then
							count[n['Has mod type']] = 1
						end
					end
					
					local number_of_mod_types = 0
					for _ in pairs(count) do 
						number_of_mod_types = number_of_mod_types + 1 
					end
					
					if number_of_mod_types > 1 then 
						tbl_caption = string.format('%s',g_frame:expandTemplate{title = 'c', args = {'mod', 'Mod group: ' .. mod_properties['Has mod group']}})
					else
						tbl_caption = string.format('%s (%s)', g_frame:expandTemplate{title = 'c', args = {'mod', p.header(mod_properties['Has stat text'])}}, mod_properties['Has mod scope'])
					end
					
					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_properties)
							:wikitext(tbl_caption)
							:done()
						:done()	
				end
				
				local mod_name = mod_properties['Has name']
				if  mod_name == '' then 
					mod_name = mod_properties['Is mod']
				end
				
				local td = mw.html.create('td')
				if extra_properties ~= nil then 
					for _, extra_property in ipairs(extra_properties) do 
						td
							:attr('width', '*')
							:wikitext(string.format('%s:&nbsp;%s ', extra_property, mod_properties[extra_property]))
							:done()
					end
				end
				
				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]]', mod_properties[1], 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;'), mod_properties['Has level requirement']))
							:done()		
						:tag('td')
							:attr('width', '*')
							:wikitext(string.format('%s', g_frame:expandTemplate{title = 'c', args = {'mod', mod_properties['Has stat text']:gsub('<br>', ', ')}}))
							:done()
						:node(td)
						:done()
					:done()
				
				last = mod_properties['Has mod group']
			end	
		end
		
		out[#out+1] = tostring(container)
    end
	
	return table.concat(out,'')
end

return p