Module:Modifier table

--[[ 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('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 = 'Domain', generation_type = 'Generation Type', required_level = '', labyrinth = 'Labyrinth', 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 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'),

normal_labyrinth = 'Normal Labyrinth', cruel_labyrinth = 'Cruel Labyrinth', merciless_labyrinth = 'Merciless Labyrinth', eternal_labyrinth = 'Eternal Labyrinth', },

drop_down_table = { collapse_all = 'Collapse all', expand_all = 'Expand all', table_intro = 'The table below displays the available modifiers for items such as', prefix = 'Prefix', suffix = 'Suffix', corrupted = 'Corrupted', enchant = 'Enchantment', elder_prefix = 'Elder prefix', elder_suffix = 'Elder suffix', shaper_prefix = 'Shaper prefix', shaper_suffix = 'Shaper suffix', delve_prefix = 'Delve prefix', delve_suffix = 'Delve suffix', crusader_prefix = 'Crusader prefix', crusader_suffix = 'Crusader suffix', eyrie_prefix = 'Redeemer prefix', eyrie_suffix = 'Redeemer suffix', basilisk_prefix = 'Hunter prefix', basilisk_suffix = 'Hunter suffix', adjudicator_prefix = 'Warlord prefix', adjudicator_suffix = 'Warlord suffix', veiled_prefix = 'Veiled prefix', veiled_suffix = 'Veiled suffix', mod_group = 'Mod group:', back_to_top = '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', }, }

-- -- 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, ' '), ', ') s = table.concat(m_util.string.split(s, ' '), ' ')

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 (page)',            v['mods.id'] or v['mods._pageName'] or '',            string.gsub( v['mods.stat_text_raw'] or 'N/A', ' ',               ', '            ) or ,            v['mods._pageName'] or         ) end return table.concat(str, ' ') end

h.tbl = {} h.tbl.display = {} h.tbl.display.factory = {} function h.tbl.display.factory.value(args) -- Format options for each field: args.options = args.options or {}

-- Separator between fields: args.delimiter = args.delimiter 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, args.delimiter)) :wikitext(table.concat(fmt_values, args.delimiter)) 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', 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'], ' ') 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, ' ') 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 = h.tbl.display.factory.value{delimiter=' '}, 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+%% 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) --[[   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 _, row_info in ipairs(row_infos) do        if type(row_info.fields) == 'function' then row_info.fields = row_info.fields end for index, field in ipairs(row_info.fields) do           row_info.options[index] = row_info.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 '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

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') local display_fields = {} for i, row_info in ipairs(row_infos) do       for j, field in ipairs(row_info.fields) do            -- Aliased name is used as keys in the results: field = m_util.string.split(field, '%s*=%s*') field = field[2] or field[1]

-- Make a new field table since mod_table.data will remain -- modified the 2nd time a function is run and then crash. if j == 1 then display_fields[i] = {} end display_fields[i][j] = field 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 i, row_info in ipairs(row_infos) do           -- this has been cast from a function in an earlier step local display = true for j, field in ipairs(display_fields[i]) do               -- this will bet set to an empty value not nil confusingly if row[field] == nil or row[field] == '' then if row_info.options[j].optional ~= true then display = false break else row[field] = nil end end end if display then row_info.display(tpl_args, frame, tr, row, display_fields[i]) 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, ' ')) :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

-- - -- 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'] = tonumber(tpl_args.domain) or 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.veiled_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                            AND mods.id LIKE "%%Master%%")                       ,                        item_tag,                        1,                        9                    ) end

return table.concat(where, ' OR ') end, },       {            header = i18n.drop_down_table.veiled_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                            AND mods.id LIKE "%%Master%%")                       ,                        item_tag,                        2,                        9                    ) end

return table.concat(where, ' OR ') 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, },       -- Reduce template expansion size by disabling enchantments, there are -- other good pages with enchantment lists: -- {           -- 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 = 'Global' for _, vv in ipairs(results[id]) do                               if vv['mod_stats.id']:find('.*local.*') ~= nil then mod_scope = 'Local' 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(       '  [%s]  [%s]  ',        i18n.drop_down_table.collapse_all,        i18n.drop_down_table.expand_all    ) out[#out+1] = expand_button out[#out+1] = string.format('%s %s.  ',        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 : 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. 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 : 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: %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( '  %s', rows['mods._pageName'], mod_name:gsub('%s', ' ') )                       )                        :done :tag('td') :attr('width', '1') :wikitext(                           string.format( '%s %s', m_game.level_requirement['short_upper'] :gsub('%s', ' '), 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(' ', ', ')                                        :gsub(' ', ' ')                               ) 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] = ' ' out[#out+1] = m_util.html.poe_color(       'normal',        string.format('%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