Module:Mod

From Path of Exile 2 Wiki
Revision as of 23:10, 17 November 2016 by >Illviljan (Added support for Has spawn chance, mainly for comparisons with poedb. Basically it is the specific modifiers spawn weight divided with the sum of all modifiers spawn weighting in that specific generation type.)
Jump to navigation Jump to search
Module documentation[view] [edit] [history] [purge]


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 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.get_spawn_chance(frame)
	-- Calculates the spawn chance of a set of mods that all have a spawn weight.
	
	-- Args
	local g_args = getArgs(frame, {
		parentFirst = true
	})
	local g_frame = util.misc.get_frame(frame)
	
	local tbl = g_args['tbl']
	local chance_multiplier = tonumber(g_args['chance_multiplier']) or 1 -- Probabillities affecting the result besides the spawn weight.
	
	local N = 0
	for i,_ in ipairs(tbl) do
		N = N + tbl[i]['Has spawn weight'] -- Total number of outcomes.
	end
	
	for i,_ in ipairs(tbl) do
		local n = tbl[i]['Has spawn weight'] -- Number of ways it can happen.
		
		tbl[i]['Has spawn chance'] = string.format("%0.2f%%", n/N * chance_multiplier*100) -- Truncated value.
	end 
	
	return tbl
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'}
	
	-- p.drop_down_table{item = 'Fishing Rod', header = 'FISH PLEASE', item_tags = 'fishing_rod', extra_properties = 'Has spawn weight, Has spawn chance'}


	-- 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']),
			chance_multiplier = 1/4, -- See Vaal orb, for the 4 possible events. 
		},
		-- [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>', m_item.item_link{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']
		
		local query = {}
		
		conditions[1] = data[i_type]['condition']
		query[#query+1] = table.concat(conditions, ' ')
		
		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
			
			
			
			for _,v in ipairs(extra_properties) do
				if v == 'Has spawn chance' then 
					item_mods[generation_type] = p.get_spawn_chance{tbl = item_mods[generation_type], chance_multiplier = data[i_type]['chance_multiplier']}
					break 
				end
			end
			
			
			
			local headers = container -- Display in <table>.
			headers
				:tag('h3')
					:wikitext(string.format('%s', data[i_type]['header']))
					:done()
				:done()
			local tbl, 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', util.html.poe_color('mod', 'Mod group: ' .. mod_properties['Has mod group']))
					else
						tbl_caption = string.format('%s (%s)', util.html.poe_color('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', util.html.poe_color('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