Path of Exile Wiki

Wiki поддерживается сообществом, поэтому подумайте над тем, чтобы внести свой вклад.

ПОДРОБНЕЕ

Path of Exile Wiki
(p.mod_table - standardize display formats a bit.)
(p.mod_table - fix broken enchantment)
Строка 34: Строка 34:
 
buff = m_util.html.abbr('Эффект', 'Идентификатор предоставленного эффекта и связанные с ним значения'),
 
buff = m_util.html.abbr('Эффект', 'Идентификатор предоставленного эффекта и связанные с ним значения'),
 
granted_skill = m_util.html.abbr('Умение', 'Идентификатор предоставленного умения'),
 
granted_skill = m_util.html.abbr('Умение', 'Идентификатор предоставленного умения'),
tags = '[[Tags]]',
+
tags = '[[Modifiers#Tags|Tags]]',
   
 
iiq = m_util.html.abbr('IIQ', 'увеличение количества найденных предметов'),
 
iiq = m_util.html.abbr('IIQ', 'увеличение количества найденных предметов'),
Строка 98: Строка 98:
   
 
local h = {}
 
local h = {}
 
function h.na_or_val(tr, value, func)
 
if value == nil or value == '' then
 
tr:wikitext(m_util.html.td.na())
 
else
 
local raw_value = value
 
if func ~= nil then
 
value = func(value)
 
end
 
tr
 
:tag('td')
 
:attr('data-sort-value', raw_value)
 
:wikitext(value)
 
:done()
 
end
 
end
 
   
 
function h.header(str)
 
function h.header(str)
Строка 186: Строка 170:
 
values[#values+1] = value.max
 
values[#values+1] = value.max
 
local opts = args.options[index] or {}
 
local opts = args.options[index] or {}
-- global colour is set, no overrides
+
-- Global colour is set, no overrides.
if args.color ~= nil then
+
if args.color ~= nil or opts.color == nil then
opts.color = args.color
 
elseif opts.color == nil then
 
 
opts.no_color = true
 
opts.no_color = true
 
end
 
end
Строка 226: Строка 208:
 
},
 
},
 
},
 
},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
 
local name
 
local name
 
if data['mods.name'] then
 
if data['mods.name'] then
Строка 245: Строка 227:
 
header = i18n.mod_table.domain,
 
header = i18n.mod_table.domain,
 
fields = {'mods.domain'},
 
fields = {'mods.domain'},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
local value = tonumber(data['mods.domain'])
+
local k = 'mods.domain'
return h.na_or_val(tr, value, function(value)
+
local i = tonumber(data[k])
return m_game.constants.mod.domains[value]['short_upper']
+
data[k] = m_game.constants.mod.domains[i]['short_upper']
  +
h.tbl.display.factory.value({})(tpl_args, frame, tr, data, fields)
end)
 
 
end,
 
end,
 
order = 2000,
 
order = 2000,
Строка 258: Строка 240:
 
header = i18n.mod_table.generation_type,
 
header = i18n.mod_table.generation_type,
 
fields = {'mods.generation_type'},
 
fields = {'mods.generation_type'},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
local value = tonumber(data['mods.generation_type'])
+
local k = 'mods.generation_type'
return h.na_or_val(tr, value, function(value)
+
local i = tonumber(data[k])
return m_game.constants.mod.domains[value]['short_upper']
+
data[k] = m_game.constants.mod.generation_types[i]['short_upper']
  +
h.tbl.display.factory.value{}(tpl_args, frame, tr, data, fields)
end)
 
 
end,
 
end,
 
order = 2001,
 
order = 2001,
Строка 294: Строка 276:
 
header = i18n.mod_table.labyrinth,
 
header = i18n.mod_table.labyrinth,
 
fields = {string.format([[
 
fields = {string.format([[
CONCAT(
+
CONCAT(
CASE mods.required_level
+
CASE mods.required_level
WHEN 32 THEN "%s"
+
WHEN 32 THEN "%s"
WHEN 53 THEN "%s"
+
WHEN 53 THEN "%s"
WHEN 66 THEN "%s"
+
WHEN 66 THEN "%s"
WHEN 75 THEN "%s"
+
WHEN 75 THEN "%s"
END
+
END
)=labyrinth_text
+
)=labyrinth_text]],
]], i18n.mod_table.normal_labyrinth, i18n.mod_table.cruel_labyrinth, i18n.mod_table.merciless_labyrinth, i18n.mod_table.eternal_labyrinth)},
+
i18n.mod_table.normal_labyrinth,
  +
i18n.mod_table.cruel_labyrinth,
  +
i18n.mod_table.merciless_labyrinth,
  +
i18n.mod_table.eternal_labyrinth)
 
},
 
display = h.tbl.display.factory.value{},
 
display = h.tbl.display.factory.value{},
 
order = 2005,
 
order = 2005,
Строка 311: Строка 297:
 
header = i18n.mod_table.stat_text,
 
header = i18n.mod_table.stat_text,
 
fields = {'mods.stat_text'},
 
fields = {'mods.stat_text'},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
 
local value
 
local value
 
-- map display type shows this in another column, remove this text to avoid clogging up the list
 
-- map display type shows this in another column, remove this text to avoid clogging up the list
Строка 337: Строка 323:
 
value = data['mods.stat_text']
 
value = data['mods.stat_text']
 
end
 
end
  +
data['mods.stat_text'] = value
 
h.na_or_val(tr, value, func)
+
h.tbl.display.factory.value{color='mod'}(tpl_args, frame, tr, data, fields)
 
end,
 
end,
 
order = 3000,
 
order = 3000,
Строка 347: Строка 333:
 
header = i18n.mod_table.buff,
 
header = i18n.mod_table.buff,
 
fields = {'mods.granted_buff_id', 'mods.granted_buff_value'},
 
fields = {'mods.granted_buff_id', 'mods.granted_buff_value'},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
local value = string.format('%s %s', data['mods.granted_buff_id'], data['mods.granted_buff_value'])
+
data['mods.stat_text'] = string.format(
return h.na_or_val(tr, value, func)
+
'%s %s',
  +
data['mods.granted_buff_id'],
  +
data['mods.granted_buff_value']
 
)
  +
h.tbl.display.factory.value{}(tpl_args, frame, tr, data, fields)
 
end,
 
end,
 
order = 4000,
 
order = 4000,
Строка 366: Строка 356:
 
header = i18n.mod_table.tags,
 
header = i18n.mod_table.tags,
 
fields = {'mods.tags'},
 
fields = {'mods.tags'},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
local value = table.concat(m_util.string.split(data['mods.tags'], ','), ', ')
+
local k = 'mods.tags'
return h.na_or_val(tr, value, func)
+
data[k] = table.concat(m_util.string.split(data[k], ',%s*'), ', ')
  +
h.tbl.display.factory.value{}(tpl_args, frame, tr, data, fields)
 
end,
 
end,
 
order = 5000,
 
order = 5000,
Строка 548: Строка 539:
 
local stat_id_map = {}
 
local stat_id_map = {}
 
for _, row in ipairs(rows) do
 
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'])}
+
stat_id_map[row['mod_stats.id']] = {
  +
min=tonumber(row['mod_stats.min']),
  +
max=tonumber(row['mod_stats.max'])
 
}
 
end
 
end
 
stats[page_id] = stat_id_map
 
stats[page_id] = stat_id_map
Строка 560: Строка 554:
 
-- Preformance optimization
 
-- Preformance optimization
 
for index, field in ipairs(tpl_args._extra_fields) do
 
for index, field in ipairs(tpl_args._extra_fields) do
field = m_util.string.split(field, '=')
+
field = m_util.string.split(field, '%s*=%s*')
 
-- field[2] will be nil if there is no alias
 
-- field[2] will be nil if there is no alias
 
tpl_args._extra_fields[index] = field[2] or field[1]
 
tpl_args._extra_fields[index] = field[2] or field[1]
Строка 571: Строка 565:
 
local tr = tbl:tag('tr')
 
local tr = tbl:tag('tr')
 
for _, row_info in ipairs(row_infos) do
 
for _, row_info in ipairs(row_infos) do
  +
-- Aliased name is used as keys in the results:
  +
for i, field in ipairs(row_info.fields) do
  +
field = m_util.string.split(field, '%s*=%s*')
  +
row_info.fields[i] = field[2] or field[1]
 
end
 
tr
 
tr
 
:tag('th')
 
:tag('th')
Строка 607: Строка 606:
 
tr = tbl:tag('tr')
 
tr = tbl:tag('tr')
   
for _, rowinfo in ipairs(row_infos) do
+
for _, row_info in ipairs(row_infos) do
 
-- this has been cast from a function in an earlier step
 
-- this has been cast from a function in an earlier step
 
local display = true
 
local display = true
for index, field in ipairs(rowinfo.fields) do
+
for index, field in ipairs(row_info.fields) do
 
-- this will bet set to an empty value not nil confusingly
 
-- this will bet set to an empty value not nil confusingly
 
if row[field] == nil or row[field] == '' then
 
if row[field] == nil or row[field] == '' then
if rowinfo.options[index].optional ~= true then
+
if row_info.options[index].optional ~= true then
 
display = false
 
display = false
 
break
 
break
Строка 622: Строка 621:
 
end
 
end
 
if display then
 
if display then
rowinfo.display(tpl_args, frame, tr, row, rowinfo.fields)
+
row_info.display(tpl_args, frame, tr, row, row_info.fields)
 
else
 
else
 
tr:wikitext(m_util.html.td.na())
 
tr:wikitext(m_util.html.td.na())

Версия от 12:50, 12 января 2020

Template info icon Документация модуля[просмотр] [править] [история] [очистить]

Реализует {{Modifier table}} и {{Item modifiers}}.

--[[
Module responsible for displaying modifiers in various ways.

]]

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

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('Наименование', 'Название модификатора, если оно доступно, или его внутренний идентификатор'),
        mod_group = m_util.html.abbr('Группа', 'При обычных обстоятельствах одновременно может отображаться только один модификатор из указанной группы'),
        mod_type = 'Тип',
        domain = '[[Modifiers#Mod_Domain|Domain]]',
        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.]]',
        labyrinth = '[[Лабиринт правителя|Лабиринт]]',
        stat_text = m_util.html.abbr('Свойства', 'Свойства модификатора и диапазон, в котором они могут действовать (если применимо)'),
        buff = m_util.html.abbr('Эффект', 'Идентификатор предоставленного эффекта и связанные с ним значения'),
        granted_skill = m_util.html.abbr('Умение', 'Идентификатор предоставленного умения'),
        tags = '[[Modifiers#Tags|Tags]]',

        iiq = m_util.html.abbr('IIQ', 'увеличение количества найденных предметов'),
        iir = m_util.html.abbr('IIR', 'повышение редкости найденных предметов'),
        pack_size = m_util.html.abbr('Размер<br>группы', 'Размер групп монстров'),

        spawn_weights = m_util.html.abbr('Spawn Weighting', 'Список применимых тегов и их значений для подсчета (т.е. расчета шансов) появления этого модификатора'),
        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'),

        normal_labyrinth = 'Первый Лабиринт',
        cruel_labyrinth = 'Жестокий Лабиринт',
        merciless_labyrinth = 'Безжалостный Лабиринт',
        eternal_labyrinth = 'Лабиринт Вечных',
    },

    drop_down_table = {
        collapse_all = 'Свернуть все',
        expand_all = 'Развернуть все',
        table_intro = 'В приведенных ниже таблицах представлены доступные [[свойства]] для таких [[предмет]]ов, как',
        prefix = 'Префикс',
        suffix = 'Суффикс',
        corrupted = 'Оскверненный',
        enchant = 'Зачарование',
        elder_prefix = 'Префикс Древнего',
        elder_suffix = 'Суффикс Древнего',
        shaper_prefix = 'Префикс Создателя',
        shaper_suffix = 'Суффикс Создателя',
        delve_prefix = 'Префикс Спуска',
        delve_suffix = 'Суффикс Спуска',
        crusader_prefix = 'Префикс Крестоносца',
        crusader_suffix = 'Суффикс Крестоносца',
        eyrie_prefix = 'Префикс Избавительницы',
        eyrie_suffix = 'Суффикс Избавительницы',
        basilisk_prefix = 'Префикс Охотника',
        basilisk_suffix = 'Суффикс Охотника',
        adjudicator_prefix = 'Префикс Вождя',
        adjudicator_suffix = 'Суффикс Вождя',
        mod_group = 'Группа свойств:',
        back_to_top = 'Наверх',
    },

    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',

        --
        -- Modifier link template
        --
        undefined_statid = 'Please define any of these stat ids: %s',
        incorrect_modid = 'Измените название с "%s" на любой из этих идентификаторов модификатора:<br>%s',
        multiple_results = 'Выберите только один из этих идентификаторов модификатора:<br>%s',
        no_results = 'Результатов не найдено.',
    },
}


--
-- Helper/Utility functions
--

local h = {}

function h.header(str)
    --[[
    This function replaces specific numbers with a generic #.
    ]]

    local s = table.concat(m_util.string.split(str, '%(%d+%.*%d*%-%d+%.*%d*%)'), '#')
    s = table.concat(m_util.string.split(s, '%d+%.*%d*'), '#')
    s = table.concat(m_util.string.split(s, '<br>'), ', ')
    s = table.concat(m_util.string.split(s, '<br />'), ' ')

   return s
end

function h.query_weights(table_name, page_ids)
    return m_cargo.map_results_to_id{
        results=m_cargo.query(
            {'mods', table_name},
            {
                'mods._pageID',
                table_name .. '.tag',
                table_name .. '.weight'
            },
            {
                where=page_ids,
                join=string.format('mods._pageID=%s._pageID', table_name),
                orderBy=string.format('mods.id ASC,%s.ordinal ASC', table_name),
            }
        ),
        field='mods._pageID',
    }
end

function h.disambiguate_mod_name(results)
    --[[
        Disambiguates results from a mods query.
    ]]
    local str = {}
    for i,v in pairs(results) do
        str[#str+1] = string.format(
            '%s - %s ([[%s|page]])',
            v['mods.id'] or v['mods._pageName'] or '',
            string.gsub(
                v['mods.stat_text_raw'] or 'N/A',
                '<br>',
                ', '
            ) or '',
            v['mods._pageName'] or ''
        )
    end
    return table.concat(str, '<br>')
end

h.tbl = {}
h.tbl.display = {}
h.tbl.display.factory = {}
function h.tbl.display.factory.value(args)
    args.options = args.options or {}

    return function(tpl_args, frame, tr, data, fields)
        local values = {}
        local fmt_values = {}

        for index, field in ipairs(fields) do
            local value = {
                min=data[field],
                max=data[field],
                base=data[field],
            }
            if value.min then
                values[#values+1] = value.max
                local opts = args.options[index] or {}
                -- Global colour is set, no overrides.
                if args.color ~= nil or opts.color == nil then
                    opts.no_color = true
                end
                fmt_values[#fmt_values+1] = m_util.html.format_value(nil, nil, value, opts)
            end
        end

        if #values == 0 then
            tr
                :wikitext(m_util.html.td.na())
        else
            local td = tr:tag('td')
            td
                :attr('data-sort-value', table.concat(values, ', '))
                :wikitext(table.concat(fmt_values, ', '))
            if args.color then
                td:attr('class', 'tc -' .. args.color)
            end
        end
    end
end

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

local mod_table = {}
mod_table.data = {
    {
        arg = 'name',
        header = i18n.mod_table.name,
        fields = {'mods._pageName', 'mods.id', 'mods.name'},
        options = {
            [3] = {
                optional=true,
           },
        },
        display = function(tpl_args, frame, tr, data, fields)
            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, fields)
            local k = 'mods.domain'
            local i = tonumber(data[k])
            data[k] = m_game.constants.mod.domains[i]['short_upper']
            h.tbl.display.factory.value({})(tpl_args, frame, tr, data, fields)
        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, fields)
            local k = 'mods.generation_type'
            local i = tonumber(data[k])
            data[k] = m_game.constants.mod.generation_types[i]['short_upper']
            h.tbl.display.factory.value{}(tpl_args, frame, tr, data, fields)
        end,
        order = 2001,
        sort_type = 'text',
    },
    {
        arg = {'group', 'mod_group'},
        header = i18n.mod_table.mod_group,
        fields = {'mods.mod_group'},
        display = h.tbl.display.factory.value{},
        order = 2002,
        sort_type = 'text',
    },
    {
        arg = {'mod_type'},
        header = i18n.mod_table.mod_type,
        fields = {'mods.mod_type'},
        display = h.tbl.display.factory.value{},
        order = 2003,
        sort_type = 'text',
    },
    {
        arg = {'level', 'required_level'},
        header = i18n.mod_table.required_level,
        fields = {'mods.required_level'},
        display = h.tbl.display.factory.value{},
        order = 2004,
    },
    {
        arg = {'enchantment', 'labyrinth'},
        header = i18n.mod_table.labyrinth,
        fields = {string.format([[
                CONCAT(
                    CASE mods.required_level
                        WHEN 32 THEN "%s"
                        WHEN 53 THEN "%s"
                        WHEN 66 THEN "%s"
                        WHEN 75 THEN "%s"
                    END
                )=labyrinth_text]],
            i18n.mod_table.normal_labyrinth,
            i18n.mod_table.cruel_labyrinth,
            i18n.mod_table.merciless_labyrinth,
            i18n.mod_table.eternal_labyrinth)
        },
        display = h.tbl.display.factory.value{},
        order = 2005,
        sort_type = 'text',
    },
    {
        arg = {'stat_text'},
        header = i18n.mod_table.stat_text,
        fields = {'mods.stat_text'},
        display = function(tpl_args, frame, tr, data, fields)
            local value
            -- 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

                value = table.concat(out, '<br>')
            else
                value = data['mods.stat_text']
            end
            data['mods.stat_text'] = value
            h.tbl.display.factory.value{color='mod'}(tpl_args, frame, tr, data, fields)
        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, fields)
            data['mods.stat_text'] = string.format(
                '%s %s',
                data['mods.granted_buff_id'],
                data['mods.granted_buff_value']
            )
            h.tbl.display.factory.value{}(tpl_args, frame, tr, data, fields)
        end,
        order = 4000,
        sort_type = 'text',
    },
    {
        arg = {'skill', 'granted_skill'},
        header = i18n.mod_table.granted_skill,
        fields = {'mods.granted_skill'},
        display = h.tbl.display.factory.value{},
        order = 4001,
        sort_type = 'text',
    },
    {
        arg = {'tags'},
        header = i18n.mod_table.tags,
        fields = {'mods.tags'},
        display = function(tpl_args, frame, tr, data, fields)
            local k = 'mods.tags'
            data[k] = table.concat(m_util.string.split(data[k], ',%s*'), ', ')
            h.tbl.display.factory.value{}(tpl_args, frame, tr, data, fields)
        end,
        order = 5000,
        sort_type = 'text',
    },
}
mod_table.stat_ids = {
    ['map_item_drop_quantity_+%'] = {
        header = i18n.mod_table.iiq,
        pattern = '%d+%% увеличение количества найденных предметов в этой области',
    },
    ['map_item_drop_rarity_+%'] = {
        header = i18n.mod_table.iir,
        pattern = '%d+%% повышение редкости найденных предметов в этой области',
    },
    ['map_pack_size_+%'] = {
        header = i18n.mod_table.pack_size,
        pattern = '%+%d+%% увеличение размера групп монстров',
    }
}
mod_table.weights = {'spawn_weights', 'generation_weights'}

function p.mod_table(frame)
    --[[
    Creates a generic table for modifiers.

    Examples
    --------
    = p.mod_table{
        q_tables='spawn_weights',
        q_join='mods._pageID=spawn_weights._pageID',
        q_where='mods.generation_type = 10 AND spawn_weights.tag = "boots" AND spawn_weights.weight > 0',
        q_orderBy='mods.id, mods.required_level',
        q_limit=100,
        stat_text=1,
        enchantment=1,
    }

    ]]


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

    -- default to enabled
    tpl_args.name = tpl_args.name or true

    for _, key in ipairs(mod_table.weights) do
        tpl_args[key] = m_util.cast.boolean(tpl_args[key])
    end

    if string.find(tpl_args.q_where, '%[%[') ~= nil then
        error('SMW leftover in where clause')
    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 required and extra tables:
    local tables = {'mods'}
    for _, v in ipairs(m_util.string.split_args(tpl_args.q_tables, {',%s*'})) do
        tables[#tables+1] = v
    end

    -- Set required and extra 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
    tpl_args._extra_fields = m_util.string.split_args(tpl_args.q_fields, {',%s*'})
    for _, v in ipairs(tpl_args._extra_fields) do
        fields[#fields+1] = v
    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 = m_cargo.query(tables, fields, query)

    if #results == 0 then
        if tpl_args.default ~= nil then
            return tpl_args.default
        else
            return 'Результатов не найдено'
        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

        stats = m_cargo.map_results_to_id{
            results=m_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',
                }
            ),
            field='mods._pageID'
        }

        -- 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

    --
    -- Display
    --

    -- Preformance optimization
    for index, field in ipairs(tpl_args._extra_fields) do
        field = m_util.string.split(field, '%s*=%s*')
        -- field[2] will be nil if there is no alias
        tpl_args._extra_fields[index] = field[2] or field[1]
    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
        -- Aliased name is used as keys in the results:
        for i, field in ipairs(row_info.fields) do
            field = m_util.string.split(field, '%s*=%s*')
            row_info.fields[i] = field[2] or field[1]
        end
        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

    for _, field in ipairs(tpl_args._extra_fields) do
        tr
            :tag('th')
                :wikitext(field)
    end

    -- Body

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

        for _, row_info in ipairs(row_infos) do
            -- this has been cast from a function in an earlier step
            local display = true
            for index, field in ipairs(row_info.fields) do
                -- this will bet set to an empty value not nil confusingly
                if row[field] == nil or row[field] == '' then
                    if row_info.options[index].optional ~= true then
                        display = false
                        break
                    else
                        row[field] = nil
                    end
                end
            end
            if display then
                row_info.display(tpl_args, frame, tr, row, row_info.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
                    if wrow[key .. '.tag'] and wrow[key .. '.weight'] then
                        weight_out[#weight_out+1] = string.format(
                            '%s %s',
                            wrow[key .. '.tag'],
                            wrow[key .. '.weight']
                        )
                    end
                end

                if #weight_out > 0 then
                    tr
                        :tag('td')
                            :wikitext(table.concat(weight_out, '<br>'))
                            :done()
                else
                    tr:wikitext(m_util.html.td.na())
                end
            end
        end

        for _, field in ipairs(tpl_args._extra_fields) do
            if row[field] then
                tr
                    :tag('td')
                        :wikitext(row[field])
            else
                tr:wikitext(m_util.html.td.na())
            end
        end
    end

    return tostring(tbl)
end




-- ---------------------------------------------------------------------
-- Modifier link
-- ---------------------------------------------------------------------


function p.modifier_link(frame)
    --[[
    Finds and links to a modifier in formatted form.

    To do list
    ----------
    * Standardize hoverbox so it can be used in multiple places easily
    and make it easy to add rows of data.

    Examples
    --------
    = p.modifier_link{"Tyrannical"}
    = p.modifier_link{"Flaring"}
    = p.modifier_link{"Dictator's"}
    = p.modifier_link{"StrDexMaster%"}
    = p.modifier_link{"LocalIncreasedPhysicalDamagePercentAndAccuracyRating8", display='max', statid='local_physical_damage_+%'}

    ]]

    -- Get template args:
    local tpl_args = getArgs(frame, {parentFirst = true})
    local frame = m_util.misc.get_frame(frame)

    -- Aliases:
    tpl_args.modid = tpl_args.modid or tpl_args.id or tpl_args[1] or ''

    -- Query cargo rows:
    tpl_args.results = m_cargo.map_results_to_id{
        results=m_cargo.query(
            {'mods', 'mod_stats', 'spawn_weights'},
            {
                'mods.name',
                'mods.stat_text',
                'mods.stat_text_raw',
                'mods.generation_type',
                'mods.tags',
                'mods._pageName',
                'mod_stats.max',
                'mod_stats.min',
                'spawn_weights.tag',
                'spawn_weights.weight',
                'mods.id',
                'mod_stats.id'
            },
            {
                join = [[
                    mods._pageName=mod_stats._pageName,
                    mods._pageName=spawn_weights._pageName
                ]],
                where = string.format(
                    [[
                        (
                            mods.name LIKE "%s" or
                            mods.id LIKE "%s" or
                            mods.stat_text LIKE "%s" or
                            mods.stat_text_raw LIKE "%s"
                        )
                        AND mod_stats.id LIKE "%%%s%%"
                    ]],
                    tpl_args.modid,
                    tpl_args.modid,
                    tpl_args.modid,
                    tpl_args.modid,
                    tpl_args.statid or '%'
                ),
                -- groupBy = 'mods._pageID, mod_stats.id, spawn_weights.tag',
            }
        ),
        field='mods._pageName',
        keep_id_field=true,
        append_id_field=true,
    }

    -- Get a sorted list that only has unique page names:
    tpl_args.results_unique = {{}}
    for i, v in ipairs(tpl_args.results) do
        tpl_args.results_unique[i] = tpl_args.results[v][1]
    end

    -- Helpful error handling:
    local err_tbl = {
        {
            bool = #tpl_args.results == 0,
            disp = {
                i18n.errors.no_results,
            }
        },
        {
            bool = #tpl_args.results_unique > 1,
            disp = {
                i18n.errors.multiple_results,
                h.disambiguate_mod_name(tpl_args.results_unique),
            },
        },
        {
            bool = tpl_args.modid ~= tpl_args.results_unique[1]['mods.id'],
            disp = {
                string.gsub(
                    i18n.errors.incorrect_modid,
                    '%%s',
                    tpl_args.modid,
                    1
                ),
                h.disambiguate_mod_name(tpl_args.results_unique),
            },
        },
    }
    for _, v in ipairs(err_tbl) do
        if v.bool then
            local cats = {'Pages with modifier link errors'}
            return m_util.html.error(
                {msg = string.format(v.disp[1], v.disp[2]) .. m_util.misc.add_category(cats)}
            )
        end
    end

    -- Display formats:
    local display = {
        abbr = {
            display = function(tpl_args, frame)
                local name = m_util.html.poe_color(
                    'mod',
                    string.format(
                        '[[%s|%s]]',
                        tpl_args.results_unique[1]['mods._pageName'],
                        tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
                    )
                )

                local tooltip_table = {
                    m_util.html.poe_color(
                        'mod',
                        tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
                    ),
                    m_util.html.poe_color(
                        'help',
                        m_game.constants.mod.generation_types[
                            tonumber(
                                tpl_args.results_unique[1]['mods.generation_type']
                            )
                        ].full
                    ),
                    -- m_util.html.poe_color(
                        -- 'help',
                       -- table.concat(
                            -- m_util.string.split(tpl_args.results_unique[1]['mods.tags'] or '', ','),
                            -- ', '
                        -- )
                    -- ),
                    m_util.html.poe_color(
                        'normal',
                        tpl_args.results_unique[1]['mods.stat_text_raw']
                    ),
                }

                local tt_tbl_fltrd = {}
                for _,v in ipairs(tooltip_table) do
                    if v ~= nil and v ~= '' then
                        tt_tbl_fltrd[#tt_tbl_fltrd+1] = v
                    end
                end
                local tooltip = table.concat(tt_tbl_fltrd, '<br>')

                return m_util.html.tooltip(name, tooltip, class)
            end,
        },
        verbose = {
            display = function(tpl_args, frame)
                return string.format(
                    '%s - %s (%s)',
                    m_util.html.poe_color(
                        'mod',
                        string.format(
                            '[[%s|%s]]',
                            tpl_args.results_unique[1]['mods._pageName'],
                            tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
                        )
                    ),
                    m_util.html.poe_color(
                        'mod',
                        string.gsub(
                            tpl_args.results_unique[1]['mods.stat_text'],
                            '<br>',
                            ', '
                        )
                    ),
                    m_game.constants.mod.generation_types[
                        tonumber(
                            tpl_args.results_unique[1]['mods.generation_type']
                        )
                    ].full
                )
            end,
        },
        stat_text = {
            display = function(tpl_args, frame)
                return m_util.html.poe_color(
                    'mod',
                    tpl_args.results_unique[1]['mods.stat_text']
                )
            end,
        },
        max = {
            display = function(tpl_args, frame)
                local statid = {}
                for _, v in ipairs(tpl_args.results[tpl_args.results_unique[1]['mods._pageName']]) do
                    statid[#statid+1] = v['mod_stats.id']
                    if tpl_args.statid == v['mod_stats.id'] then
                        return v['mod_stats.max']
                    end
                end

                if tpl_args.statid == nil then
                    return m_util.html.error(
                        {
                            msg = string.format(
                                i18n.errors.undefined_statid,
                                table.concat(statid, ', ')
                            )
                        }
                    )
                end
            end
        },
        min = {
            display = function(tpl_args, frame)
                local statid = {}
                for _, v in ipairs(tpl_args.results[tpl_args.results_unique[1]['mods._pageName']]) do
                    statid[#statid+1] = v['mod_stats.id']
                    if tpl_args.statid == v['mod_stats.id'] then
                        return v['mod_stats.min']
                    end
                end

                if tpl_args.statid == nil then
                    return m_util.html.error(
                        {
                            msg = string.format(
                                i18n.errors.undefined_statid,
                                table.concat(statid, ', ')
                            )
                        }
                    )
                end
            end
        },
    }

    return display[tpl_args.display or 'abbr'].display(tpl_args, frame)
end




-- ---------------------------------------------------------------------
-- Drop down list
-- ---------------------------------------------------------------------

function h.get_spawn_chance(frame)
    --[[
    Calculates the spawn chance of a set of mods that all have a
    spawn weight.

    ]]

    -- Get template arguments:
    local tpl_args = getArgs(frame, {parentFirst=true})
    local frame = m_util.misc.get_frame(frame)

    -- Get the table:
    local tbl = tpl_args['tbl']

    -- Probabilities affecting the result besides the spawn weight:
    local chance_multiplier = tonumber(tpl_args['chance_multiplier']) or 1

    -- Total number of outcomes.
    local N = 0
    for i,_ in ipairs(tbl) do
        N = N + tbl[i]['spawn_weights.weight']
    end

    for i,_ in ipairs(tbl) do
        -- Number of ways it can happen:
        local n = tbl[i]['spawn_weights.weight']

        -- Truncated value:
        tbl[i]['spawn_weights.chance'] = string.format(
            "%0.2f%%",
            n/N * chance_multiplier*100
        )
    end

    return tbl
end

function p.drop_down_table(frame)
    --[[
    This function queries mods that can spawn on an item. It compares
    the item tags and the spawn weight tags. If there's a match and
    the spawn weight is larger than zero, then that mod is added to a
    drop down list.

    To Do:
    * Add support to:
        * Forsaken masters
        * Bestiary
    * 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 [Collapsible, default=Expanded]
            # to Damage [Collapsible, default=Collapsed]
                3 to Damage
                5 to Damage
    * Add a where condition that somehow filters out mods that obviously
      wont match with the item. spawn_weights.weight>0 isn't enough due
      to possible edge cases.


    Examples:
    Weapons
    = p.drop_down_table{item = 'Rusted Hatchet', header = 'One Handed Axes'}
    = p.drop_down_table{item = 'Stone Axe', header = 'Two Handed Axes'}

    Accessories
    = p.drop_down_table{item = 'Amber Amulet', header = 'Amulets'}

    Jewels
    = p.drop_down_table{item = 'Cobalt Jewel', header = 'Jewels'}

    Armour
    = p.drop_down_table{item = 'Plate Vest', header = 'Body armours'}

    Boots
    = p.drop_down_table{item = 'Iron Greaves', header = 'Boots'}

    = p.drop_down_table{
        item = 'Fishing Rod',
        header = 'FISH PLEASE',
        item_tags = 'fishing_rod',
        extra_fields = 'mods.tags'
    }

    = p.drop_down_table{
        item = 'Fishing Rod',
        item_tags = 'axe, one_hand_weapon, onehand, weapon, default',
        extra_item_tags = 'fishing_rod'
    }

    = p.drop_down_table{
        item = 'Vaal Blade',
    }

    ]]

    -- Get template arguments:
    local tpl_args = getArgs(frame, {parentFirst=true})
    local frame = m_util.misc.get_frame(frame)

    -- Format the cargo query:
    local where
    for _,v in ipairs({
            {tpl_args.page, 'items._pageName = "%s"'},
            {tpl_args.item, 'items.name = "%s"'},
    }) do
        if v[1] ~= nil then
            where = string.format(v[2], v[1])
            break
        end
    end

    local item_info = m_cargo.query(
        {'items'},
        {
            'items.name',
            'items.tags',
            'items.class_id',
            'items.inventory_icon',
            'items.html',
            'items.release_version',
            'items._pageName'
        },
        {
            where=where,
            groupBy='items._pageName',
            orderBy='items.name, items.release_version DESC',
        }
    )[1]

    -- Set the item class as key and the corresponding mod domain as
    -- value:
    local class_to_domain = {
        ['LifeFlask']=2,
        ['ManaFlask']=2,
        ['HybridFlask']=2,
        ['UtilityFlask']=2,
        ['UtilityFlaskCritical']=2,
        ['Map']=5,
        ['Jewel']=10,
        ['Leaguestone']=12,
        ['AbyssJewel']=13,
    }
    -- Get the domain, if it's not defined in the table assume it's
    -- in the item domain.
    item_info['items.domain'] = class_to_domain[item_info['items.class_id']] or 1

    -- Convert the mod domain number to understandable text:
    item_info['items.domain_text'] = m_game.constants.mod.domains[item_info['items.domain']]['short_lower']

    -- Format item tags:
    tpl_args.item_tags = m_util.string.split_args(
        tpl_args.item_tags or item_info['items.tags'],
        {sep=',%s*'}
    )
    for _,v in ipairs(m_util.string.split_args(tpl_args.extra_item_tags, {sep=',%s*'})) do
        tpl_args.item_tags[#tpl_args.item_tags+1] = v
    end

    -- Format extra fields:
    local extra_fields = m_util.string.split_args(tpl_args.extra_fields, {sep=',%s*'})


    -- Get tags that are appended to special items:
    local elder_tag = m_game.constants.item.classes[item_info['items.class_id']]['elder_tag']
    local shaper_tag = m_game.constants.item.classes[item_info['items.class_id']]['shaper_tag']
    local crusader_tag = m_game.constants.item.classes[item_info['items.class_id']]['crusader_tag']
    local eyrie_tag = m_game.constants.item.classes[item_info['items.class_id']]['eyrie_tag']
    local basilisk_tag = m_game.constants.item.classes[item_info['items.class_id']]['basilisk_tag']
    local adjudicator_tag = m_game.constants.item.classes[item_info['items.class_id']]['adjudicator_tag']

    -- Create drop down lists in these sections and query in these
    -- generation types.
    local section = {
        {
            header = i18n.drop_down_table.prefix,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        1,
                        item_info['items.domain']
                    )
                end

                return table.concat(where, ' OR ')
            end,
        },
        {
            header = i18n.drop_down_table.suffix,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        2,
                        item_info['items.domain']
                    )
                end

                return table.concat(where, ' OR ')
            end,
        },
        {
            tags = {elder_tag},
            header = i18n.drop_down_table.elder_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    elder_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {elder_tag},
            header = i18n.drop_down_table.elder_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    elder_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {shaper_tag},
            header = i18n.drop_down_table.shaper_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    shaper_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {shaper_tag},
            header = i18n.drop_down_table.shaper_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    shaper_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            header = i18n.drop_down_table.delve_prefix,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        1,
                        16
                    )
                end

                return table.concat(where, ' OR ')
            end,
        },
        {
            header = i18n.drop_down_table.delve_suffix,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        2,
                        16
                    )
                end

                return table.concat(where, ' OR ')
            end,
        },
        {
            tags = {crusader_tag},
            header = i18n.drop_down_table.crusader_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    crusader_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {crusader_tag},
            header = i18n.drop_down_table.crusader_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    crusader_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {eyrie_tag},
            header = i18n.drop_down_table.eyrie_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    eyrie_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {eyrie_tag},
            header = i18n.drop_down_table.eyrie_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    eyrie_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {basilisk_tag},
            header = i18n.drop_down_table.basilisk_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    basilisk_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {basilisk_tag},
            header = i18n.drop_down_table.basilisk_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    basilisk_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {adjudicator_tag},
            header = i18n.drop_down_table.adjudicator_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    adjudicator_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {adjudicator_tag},
            header = i18n.drop_down_table.adjudicator_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    adjudicator_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            header = i18n.drop_down_table.corrupted,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        5,
                        item_info['items.domain']
                    )
                end

                return table.concat(where, ' OR ')
            end,
            chance_multiplier = 1/4, -- See Vaal orb, for the 4 possible events.
            is_implicit = true,
        },
        {
            header = i18n.drop_down_table.enchant,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        10,
                        item_info['items.domain']
                    )
                end

                return table.concat(where, ' OR ')
            end,
            is_implicit = true,
        },
    }

    -- Save the original tag format:
    local item_tags_orig = {}
    for i,v in ipairs(tpl_args.item_tags) do
        item_tags_orig[i] = v
    end

    local item_mods = {}
    local mod_group_counter = {}
    mod_group_counter['all'] = {}
    local extra_fieldss = {}
    local table_index_base = -1
    for _, sctn in ipairs(section) do
        item_mods[sctn['header']] = {}

        -- Preallocate the mod group counter, implicit and explicit mods
        -- are counted separetely because they can spawn together:
        mod_group_counter[sctn['header']] = {}
        local adj = 'explicit'
        if sctn['is_implicit'] then
            adj = 'implicit'
        end
        for _, header in ipairs({sctn['header'], 'all'}) do
            if mod_group_counter[header][adj] == nil then
                mod_group_counter[header][adj] = {}
            end
        end

        local continue = true
        local current_tags
        if sctn['tags'] then
            -- some item classes do not have shaper/elder items, so the table
            -- will not contain any tags:
            if #sctn['tags'] == 0 then
                continue = false
            else
                current_tags = sctn['tags']
            end
        else
            current_tags = {}
            -- Reset to original tags:
            for i,v in ipairs(item_tags_orig) do
                current_tags[i] = v
            end
        end

        if continue then
            -- Cargo preparation:
            local q_fields = {
                'mods._pageName',
                'mods.name',
                'mods.id',
                'mods.required_level',
                'mods.generation_type',
                'mods.domain',
                'mods.mod_group',
                'mods.mod_type',
                'mods.stat_text',
                'mods.stat_text_raw',
                'mods.tags',
                'mod_stats.id',
                'spawn_weights.tag',
                'spawn_weights.weight',
                'spawn_weights.ordinal',
                'spawn_weights._pageName'
            }
            for i, v in ipairs(extra_fields) do
                q_fields[#q_fields+1] = v
            end

            -- Query mods and map the results to the pagename:
            local results = m_cargo.map_results_to_id{
                results=m_cargo.query(
                    {'mods', 'spawn_weights', 'mod_stats'},
                    q_fields,
                    {
                        join = [[
                            mods._pageName=spawn_weights._pageName,
                            mods._pageName=mod_stats._pageName
                        ]],
                        where = sctn['where'](tpl_args, frame, value),
                        groupBy = [[
                            mods._pageName,
                            spawn_weights.tag,
                            spawn_weights.weight
                        ]],
                        orderBy = [[
                            mods.generation_type,
                            mods.mod_group,
                            mods.mod_type,
                            mods.required_level,
                            mods._pageName,
                            spawn_weights.ordinal
                        ]],
                    }
                ),
                field='mods._pageName',
                keep_id_field=true,
                append_id_field=true,
            }

            if #results > 0 then
                -- Loop through all found modifiers:
                local last
                for _, id in ipairs(results) do
                    -- Loop through all the modifier tags until they match
                    -- the item tags:
                    local j = 0
                    local tag_match_stop
                    repeat
                        j = j+1
                        local mod_tag = results[id][j]['spawn_weights.tag']
                        local mod_tag_weight = tonumber(
                            results[id][j]['spawn_weights.weight']
                        )

                        -- Loop through the item tags until it matches the
                        -- spawn weight tag and the mod tag has a value larger than
                        -- zero:
                        local y = 0
                        local tag_match_add = false
                        repeat
                            y = y+1
                            tag_match_stop = ((mod_tag == current_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (results[id][j] == nil)
                            tag_match_add  =  (mod_tag == current_tags[y]) and ((mod_tag_weight or -1) > 0)
                        until tag_match_stop or y == #current_tags

                        -- If there's a match then save that mod and other
                        -- interesting information:
                        if tag_match_add then

                            -- Assume that the mod is global then go through
                            -- all the stat ids and check if any of the
                            -- stats are local:
                            local mod_scope = 'Глобальный'
                            for _, vv in ipairs(results[id]) do
                                if vv['mod_stats.id']:find('.*local.*') ~= nil then
                                    mod_scope = 'Локальный'
                                end
                            end

                            -- Save the matching modifier tag:
                            local a = #item_mods[sctn['header']]
                            item_mods[sctn['header']][a+1] = results[id][j]

                            -- Save other interesting fields:
                            item_mods[sctn['header']][a+1]['mods.scope'] = mod_scope
                            item_mods[sctn['header']][a+1]['spawn_weight.idx_match'] = j
                            item_mods[sctn['header']][a+1]['mods.add'] = tag_match_add
                            item_mods[sctn['header']][a+1]['mods.stop'] = tag_match_stop

                            -- Count the mod groups:
                            local group = item_mods[sctn['header']][a+1]['mods.mod_group'] or 'nil_group'
                            for _, header in ipairs({sctn['header'], 'all'}) do
                                if mod_group_counter[header][adj][group] == nil then
                                    mod_group_counter[header][adj][group] = {}
                                end
                                local tp = results[id][j]['mods.mod_type']
                                local bef = mod_group_counter[header][adj][group][tp] or 0
                                mod_group_counter[header][adj][group][tp] = 1 + bef
                            end
                        end
                    until tag_match_stop
                end

                -- If the user wants to see the spawn chance then do the
                -- calculations and save that result as well:
                if tpl_args.spawn_chance ~= nil then
                    extra_fields[#extra_fields+1] = 'spawn_weights.chance'
                    item_mods[sctn['header']] = h.get_spawn_chance{
                        tbl = item_mods[sctn['header']],
                        chance_multiplier = sctn['chance_multiplier']
                    }
                end

                extra_fieldss[sctn['header']] = extra_fields
            end
        end
    end


    --
    -- Display the item mods
    --

    -- Introductory text:
    local out = {}
    out[#out+1] = string.format(
        '==%s== \n',
        tpl_args['header'] or table.concat(tpl_args.item_tags, ', ')
    )
    local expand_button = string.format(
        '<div style="float: right; text-align:center"><div class="mw-collapsible-collapse-all" style="cursor:pointer;">[%s]</div><hr><div class="mw-collapsible-expand-all" style="cursor:pointer;">[%s]</div></div>',
        i18n.drop_down_table.collapse_all,
        i18n.drop_down_table.expand_all
    )
    out[#out+1] = expand_button
    out[#out+1] = string.format('%s %s.<br><br><br>',
        i18n.drop_down_table.table_intro,
        f_item_link{
            page=item_info['items._pageName'],
            name=item_info['items.name'],
            inventory_icon=item_info['items.inventory_icon'] or '',
            html=item_info['items.html'] or '',
            skip_query=true
        }
    )

    -- Loop through the sections:
    for _, sctn in ipairs(section) do
        local extra_fields = extra_fieldss[sctn['header']]
        local adj = 'explicit'
        if sctn['is_implicit'] then
            adj = 'implicit'
        end

        -- Create html container:
        local container = mw.html.create('div')
            :attr('style', 'vertical-align:top; display:inline-block;')

        -- Create the drop down table with <table></table>:
        local headers = container
        if #item_mods[sctn['header']] > 0 then
            headers
                :tag('h3')
                    :wikitext(string.format('%s', sctn['header']))
                    :done()
                :done()
        end

        local total_mod_groups = 0
        for _ in pairs(mod_group_counter[sctn['header']][adj]) do
            total_mod_groups = 1+total_mod_groups
        end

        -- Loop through and add all matching mods to the <table>.
        local tbl, last_group, last_type
        for _, rows in ipairs(item_mods[sctn['header']]) do

            -- If the last mod group is different to the current
            -- mod group then assume the mod isn't related and start
            -- a new drop down list, if there's only one mod group
            -- then use mod type instead:
            if rows['mods.mod_group'] ~= last_group or (total_mod_groups == 1 and rows['mods.mod_type'] ~= last_type) then
                -- Check through all the mods and see if there are
                -- multiple mod types within the same mod group:
                local count = {}
                for _, n in ipairs(item_mods[sctn['header']]) do

                    -- If the mod has the same mod group, then add
                    -- the mod type to the counter. Only unique mod
                    -- types matter so the number is just a dummy
                    -- value:
                    if n['mods.mod_group'] == rows['mods.mod_group'] then
                        count[n['mods.mod_type']] = 1
                    end
                end

                -- Calculate how many unique mod types with the
                -- same mod group there are for all explicit or implicit
                -- sections since a mod with the same mod group can't
                -- spawn. Doesn't matter if it's prefix or suffix.
                local number_of_mod_types = 0
                for _ in pairs(mod_group_counter['all'][adj][rows['mods.mod_group']]) do
                    number_of_mod_types = 1 + number_of_mod_types
                end

                -- If there are multiple unique mod types with the
                -- same mod group then change the style of the drop
                -- down list to indicate it, if there's only one
                -- mod group in the generation type then ignore it:
                local table_index_mod_group
                if number_of_mod_types > 1 and total_mod_groups > 1 then
                    table_index_mod_group = table.concat(
                        {string.byte(rows['mods.mod_group'], 1, #rows['mods.mod_group'])},
                        ''
                    )

                    tbl_caption = string.format(
                        '%s',
                        m_util.html.poe_color(
                            'stat',
                            string.format(
                                '%s %s',
                                i18n.drop_down_table.mod_group,
                                rows['mods.mod_group']
                            )
                        ) or ''
                    )

                else
                    tbl_caption = string.format(
                        '%s (%s)',
                        m_util.html.poe_color(
                            'mod',
                            h.header(rows['mods.stat_text_raw'])
                        ) or '',
                        rows['mods.scope']
                    )
                end

                -- Create a table index for handling the collapsible:
                table_index_base = table_index_base+1
                if table_index_mod_group ~= nil then
                    table_index = table_index_mod_group
                else
                    table_index = table_index_base
                end

                -- Add class and style to the <table>:
                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', table_index)
                        )
                        :attr('style',
                            'text-align:left; line-height:1.40em; border-bottom:1pt solid dimgrey;'
                        )
                        :attr('colspan', '3' .. #extra_fields)
                        :wikitext(tbl_caption)
                        :done()
                    :done()
            end

            -- If the mod has no name then use the mod type:
            local mod_name = rows['mods.name'] or ''
            if  mod_name == '' then
                mod_name = rows['mods.mod_type']
            end

            -- Check if there are any extra properties to show in
            -- the drop down list and then add a cell for that,
            -- add this node at the end of the table row:
            local td = mw.html.create('td')
            if extra_fields ~= nil then
                for _, extra_field in ipairs(extra_fields) do
                    td
                        :attr('width', '*')
                        :wikitext(string.format(
                            '%s:&nbsp;%s ',
                            extra_field,
                            rows[extra_field] or ''
                            )
                        )
                        :done()
                end
            end

            -- Style mods.tags:
            local mods_tags = table.concat(
                m_util.string.split_args(rows['mods.tags'], {sep=',%s*'}),
                ', '
            )
            if mods_tags ~= '' then
                mods_tags = m_util.html.tooltip('*', mods_tags, class)
            end

            -- Add a table row with the interesting properties that
            -- modifier has:
            tbl
                :tag('tr')
                    :attr('class', 'mw-collapsible mw-collapsed')
                    :attr(
                        'id',
                        string.format('mw-customcollapsible-%s', table_index)
                    )
                    :tag('td')
                        :attr('width', '160')
                        :wikitext(
                            string.format(
                                '&nbsp;&nbsp;&nbsp;[[%s|%s]]',
                                rows['mods._pageName'],
                                mod_name:gsub('%s', '&nbsp;')
                            )
                        )
                        :done()
                    :tag('td')
                        :attr('width', '1')
                        :wikitext(
                            string.format(
                                '%s&nbsp;%s',
                                m_game.level_requirement['short_upper']
                                    :gsub('%s', '&nbsp;'),
                                rows['mods.required_level']
                            )
                        )
                        :done()
                    :tag('td')
                        :attr('width', '*')
                        :wikitext(
                            string.format(
                                '%s%s',
                                m_util.html.poe_color(
                                    'mod',
                                    rows['mods.stat_text']
                                        :gsub('<br>', ', ')
                                        :gsub('<br />', ' ')
                                ) or '',
                                mods_tags
                            )
                        )
                        :done()
                    :node(td)
                    :done()
                :done()

            -- Save the last mod group for later comparison:
            last_group = rows['mods.mod_group']
            last_type = rows['mods.mod_type']
        end
        out[#out+1] = tostring(container)
    end

    -- Outro text:
    out[#out+1] = '<br>'
    out[#out+1] = m_util.html.poe_color(
        'normal',
        string.format('[[#Top|%s]]', i18n.drop_down_table.back_to_top)
    )

    return table.concat(out,'')
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