Path of Exile Wiki

Please consider helping keep the wiki up to date. Check the to-do list of updates needed for version 3.14.0.

Game data exports will becoming later as the technical changes in addition to regular changes take some more time.

READ MORE

Path of Exile Wiki
Advertisement
Module documentation[view] [edit] [history] [purge]

Module implementing {{Modifier table}} with Cargo support.

-- Module responsible for the old fashioned mod tables

local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')

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 = {
    mod_table = {
        name = m_util.html.abbr('Name', 'Name of the modifier if available or its internal identifier instead'),
        mod_group = m_util.html.abbr('Group', 'Only one modifier from the specified group can appear at a time under normal circumstances'),
        mod_type = 'Type',
        domain = '[[Modifiers#Mod_Domain|Domain]]',
        generation_type = '[[Modifiers#Mod_Generation_Type|Generation Type]]',
        required_level = '[[Image:Level_up_icon_small.png|link=|For generated item/monster modifiers the minimum item/monster level respectively. Some generation types may not require this condition to be met, however item level restrictions may be raised to 80% of this value.]]',
        stat_text = m_util.html.abbr('Stats', 'Stats of the modifier and the range they can roll in (if applicable)'),
        buff = m_util.html.abbr('Buff', 'ID of the buff granted and the values associated'),
        granted_skill = m_util.html.abbr('Skill', 'ID of the skill granted'),
        tags = '[[Tags]]',
        
        iiq = m_util.html.abbr('IIQ', 'increased Quantity of Items found in this Area'),
        iir = m_util.html.abbr('IIR', 'increased Rarity of Items found in this Area'),
        pack_size = m_util.html.abbr('Pack<br>Size', 'Monster pack size'),
        
        spawn_weights = m_util.html.abbr('Spawn Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
        generation_weights = m_util.html.abbr('Generation Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
    },
    
    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',
    },
}


--
-- Helper/Utility functions
--

local h = {}

function h.query_weights(table_name, page_ids)
    results = cargo.query(
        string.format('mods,%s', table_name),
        string.format('mods._pageID,%s.tag,%s.weight', table_name, table_name),
        {
            where=page_ids,
            join=string.format('mods._pageID=%s._pageID', table_name),
            orderBy=string.format('mods.id ASC,%s.ordinal ASC', table_name),
            limit=5000,
        }
    )
    if #results == 5000 then
        error('Hit maximum cargo results')
    end
    
    return m_util.cargo.map_results_to_id{results=results, table_name='mods'}
end

-- ----------------------------------------------------------------------------
-- Template: Mod table
-- ----------------------------------------------------------------------------

local mod_table = {}
mod_table.data = {
    {
        arg = nil,
        header = i18n.mod_table.name,
        fields = {'mods._pageName', 'mods.id', 'mods.name'},
        options = {
            [3] = {
                optional=true,
           },
        },
        display = function(tpl_args, frame, tr, data)
            local name
            if data['mods.name'] then
                name = data['mods.name']
            else
                name = data['mods.id']
            end
            tr
                :tag('td')
                    :wikitext(string.format('[[%s|%s]]', data['mods._pageName'], name))
        end,
        order = 1000,
        sort_type = 'text',
    },
    {
        arg = 'domain',
        header = i18n.mod_table.domain,
        fields = {'mods.domain'},
        display = function(tpl_args, frame, tr, data)
            local value = data['mods.domain']
            tr
                :tag('td')
                    :attr('data-sort-value', value)
                    :wikitext(m_game.constants.mod.domains[tonumber(value)]['short_upper'])
        end,
        order = 2000,
        sort_type = 'text',
    },
    {
        arg = 'generation_type',
        header = i18n.mod_table.generation_type,
        fields = {'mods.generation_type'},
        display = function(tpl_args, frame, tr, data)
            local value = data['mods.generation_type']
            tr
                :tag('td')
                    :attr('data-sort-value', value)
                    :wikitext(m_game.constants.mod.generation_types[tonumber(value)]['short_upper'])
        end,
        order = 2001,
        sort_type = 'text',
    },
    {
        arg = {'group', 'mod_group'},
        header = i18n.mod_table.mod_group,
        fields = {'mods.mod_group'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(data['mods.mod_group'])
        end,
        order = 2002,
        sort_type = 'text',
    },
    {
        arg = {'mod_type'},
        header = i18n.mod_table.mod_type,
        fields = {'mods.mod_type'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(data['mods.mod_type'])
        end,
        order = 2003,
        sort_type = 'text',
    },
    {
        arg = {'level', 'required_level'},
        header = i18n.mod_table.required_level,
        fields = {'mods.required_level'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(data['mods.required_level'])
        end,
        order = 2004,
    },
    {
        arg = {'stat_text'},
        header = i18n.mod_table.stat_text,
        fields = {'mods.stat_text'},
        display = function(tpl_args, frame, tr, data)
            local text
            -- map display type shows this in another column, remove this text to avoid clogging up the list
            if tpl_args.type == 'map' then
                local texts = m_util.string.split(data['mods.stat_text'], '<br>')
                local out = {}
                
                local valid
                for _, v in ipairs(texts) do
                    valid = true
                    for _, data in pairs(mod_table.stat_ids) do
                        if string.find(v, data.pattern) ~= nil then
                            valid = false
                            break
                        end
                    end
                    
                    if valid then
                        table.insert(out, v)
                    end
                end
                
                text = table.concat(out, '<br>')
            else
                text = data['mods.stat_text']
            end
            
            tr
                :tag('td')
                    :wikitext(text)
        end,
        order = 3000,
        sort_type = 'text',
    },

    {
        arg = 'buff',
        header = i18n.mod_table.buff,
        fields = {'mods.granted_buff_id', 'mods.granted_buff_value'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(string.format('%s %s', data['mods.granted_buff_id'], data['mods.granted_buff_value']))
        end,
        order = 4000,
        sort_type = 'text',
    },
    {
        arg = {'skill', 'granted_skill'},
        header = i18n.mod_table.granted_skill,
        fields = {'mods.granted_skill'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(data['mods.granted_skill'])
        end,
        order = 4001,
        sort_type = 'text',
    },
    {
        arg = {'tags'},
        header = i18n.mod_table.tags,
        fields = {'mods.tags'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(table.concat(m_util.string.split(data['mods.tags'], ','), ', '))
        end,
        order = 5000,
        sort_type = 'text',
    },
}
mod_table.stat_ids = {
    ['map_item_drop_quantity_+%'] = {
        header = i18n.mod_table.iiq,
        pattern = '%d+%% increased Quantity of Items found in this Area',
    },
    ['map_item_drop_rarity_+%'] = {
        header = i18n.mod_table.iir,
        pattern = '%d+%% increased Rarity of Items found in this Area',
    },
    ['map_pack_size_+%'] = {
        header = i18n.mod_table.pack_size,
        pattern = '%+%d+%% Monster pack size',
    }
}
mod_table.weights = {'spawn_weights', 'generation_weights'}

function p.mod_table(frame)
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    for _, key in ipairs(mod_table.weights) do
        tpl_args[key] = m_util.cast.boolean(tpl_args[key])
    end
    
    local row_infos = {}
    for _, row_info in ipairs(mod_table.data) do
        local enabled = false
        if row_info.arg == nil then
            enabled = true
        elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then
            enabled = true
        elseif type(row_info.arg) == 'table' then 
            for _, argument in ipairs(row_info.arg) do
                if m_util.cast.boolean(tpl_args[argument]) then
                    enabled = true
                    break
                end
            end
        end
        
        if enabled then
            row_info.options = row_info.options or {}
            row_infos[#row_infos+1] = row_info
        end
    end
    
    -- sort the rows
    table.sort(row_infos, function (a, b)
        return (a.order or 0) < (b.order or 0)
    end)
    
    -- Set tables
    local tables = 'mods' 
    if tpl_args.q_tables then
        tables = tables .. ',' .. tpl_args.q_tables
    end
    
    
    -- Set required fields
    local fields = {
        'mods._pageID',
    }
    for _, rowinfo in ipairs(row_infos) do
        if type(rowinfo.fields) == 'function' then
            rowinfo.fields = rowinfo.fields()
        end
        for index, field in ipairs(rowinfo.fields) do
            rowinfo.options[index] = rowinfo.options[index] or {}
            fields[#fields+1] = field
        end
    end
    
    -- Parse query arguments
    local query = {
        -- Workaround: fix duplicates
        groupBy='mods._pageID',
    }
    for key, value in pairs(tpl_args) do 
        if string.sub(key, 0, 2) == 'q_' then
            query[string.sub(key, 3)] = value
        end
    end
    
    local results = cargo.query(
        tables,
        table.concat(fields, ','),
        query
    )
    
    if #results == 0 then
        if tpl_args.default ~= nil then
            return tpl_args.default
        else
            return 'No results found'
        end
    end
    
    -- this might be needed in other queries, currently not checking if it's actually needed
    -- because performance impact should be neglible
    local page_ids = {}
    for _, row in ipairs(results) do
        page_ids[#page_ids+1] = string.format('mods._pageID="%s"', row['mods._pageID'])
    end
    page_ids = table.concat(page_ids, ' OR ')
    
    local weights = {}
    for _, key in ipairs(mod_table.weights) do
        if tpl_args[key] then
            weights[key] = h.query_weights(key, page_ids)
        end
    end
    
    local stats
    if tpl_args.type == 'map' then
        local query_stat_ids = {}
        for k, _ in pairs(mod_table.stat_ids) do
            query_stat_ids[#query_stat_ids+1] = string.format('mod_stats.id="%s"', k)
        end
        
        local stat_results = cargo.query(
            'mods,mod_stats',
            'mods._pageID,mod_stats.id,mod_stats.min,mod_stats.max',
            {
                where=string.format('(%s) AND (%s)', page_ids, table.concat(query_stat_ids, ' OR ')),
                join='mods._pageID=mod_stats._pageID',
                orderBy='mods.id ASC',
                limit=5000,
            }
        )
        if #stat_results == 5000 then
            error('Hit maximum cargo results')
        end
        
        stats = m_util.cargo.map_results_to_id{results=stat_results, table_name='mods'}
        -- In addition map stats to stat <-> min/max pairs
        for page_id, rows in pairs(stats) do
            local stat_id_map = {}
            for _, row in ipairs(rows) do
                stat_id_map[row['mod_stats.id']] = {min=tonumber(row['mod_stats.min']), max=tonumber(row['mod_stats.max'])}
            end
            stats[page_id] = stat_id_map
        end
    end
    
    local tbl = mw.html.create('table')
    tbl:attr('class', 'wikitable sortable modifier-table')
    
    -- Header
    
    local tr = tbl:tag('tr')
    for _, row_info in ipairs(row_infos) do
        tr
            :tag('th')
                :attr('data-sort-type', row_info.sort_type or 'number')
                :wikitext(row_info.header)
                :done()
    end
    
    if tpl_args.type == 'map' then
        for stat_id, data in pairs(mod_table.stat_ids) do
            tr
                :tag('th')
                    :attr('data-sort-type', 'number')
                    :wikitext(data.header)
                    :done()
        end
    end
    
    for _, key in ipairs(mod_table.weights) do
        if tpl_args[key] then
            tr
                :tag('th')
                    :wikitext(i18n.mod_table[key])
        end
    end
    
    -- Body
    
    for _, row in ipairs(results) do
        tr = tbl:tag('tr')
                
        for _, rowinfo in ipairs(row_infos) do
            -- this has been cast from a function in an earlier step
            local display = true
            for index, field in ipairs(rowinfo.fields) do
                -- this will bet set to an empty value not nil confusingly
                if row[field] == '' then
                    if rowinfo.options[index].optional ~= true then
                        display = false
                        break
                    else
                        row[field] = nil
                    end
                end
            end
            if display then
                rowinfo.display(tpl_args, frame, tr, row, rowinfo.fields)
            else
                tr:wikitext(m_util.html.td.na())
            end
        end
        
        if tpl_args.type == 'map' then
            for stat_id, data in pairs(mod_table.stat_ids) do
                local stat_data = stats[row['mods._pageID']]
                if stat_data and stat_data[stat_id] then
                    local v = stat_data[stat_id]
                    local text
                    if v.min == v.max then
                        text = v.min
                    else
                        text = string.format('(%s to %s)', v.min, v.max)
                    end
                    tr
                        :tag('td')
                            :attr('data-sort-value', (v.min+v.max)/2)
                            :wikitext(string.format('%s%%', text))
                            :done()
                else
                    tr:wikitext(m_util.html.td.na())
                end
            end
        end
        
        for _, key in ipairs(mod_table.weights) do
            if tpl_args[key] then
                local weight_out = {}
                for _, wrow in ipairs(weights[key][row['mods._pageID']]) do
                    weight_out[#weight_out+1] = string.format('%s %s', wrow[key .. '.tag'], wrow[key .. '.weight'])
                end
                if #weight_out > 1 then
                    tr
                            :tag('td')
                                :wikitext(table.concat(weight_out, '<br>'))
                                :done()
                else    
                    tr:wikitext(m_util.html.td.na())
                end
            end
        end
    end
    
    return tostring(tbl)
end

-- ----------------------------------------------------------------------------
-- Debug functions
-- ----------------------------------------------------------------------------

p.debug = {}
function p.debug.tbl_data(tbl)
    keys = {}
    for _, data in ipairs(mod_table.data) do
        if type(data.arg) == 'string' then 
            keys[data.arg] = 1
        elseif type(data.arg) == 'table' then
            for _, arg in ipairs(data.arg) do
                keys[arg] = 1
            end
        end
    end
    
    for _, key in ipairs(mod_table.weights) do
        keys[key] = 1
    end
    
    local out = {}
    for key, _ in pairs(keys) do
        out[#out+1] = string.format("['%s'] = '1'", key)
    end
    
    return table.concat(out, ', ')
end

-- ----------------------------------------------------------------------------
--
-- ----------------------------------------------------------------------------

return p