Module:Modifier compatibility: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
(still need these bypass for watchstone, Sentinel and heist stuff.)
No edit summary
 
(12 intermediate revisions by 3 users not shown)
Line 8: Line 8:
require('Module:No globals')
require('Module:No globals')
local m_util = require('Module:Util')
local m_util = require('Module:Util')
local m_item_util = require('Module:Item util')
local m_cargo = require('Module:Cargo')
local m_cargo = require('Module:Cargo')


Line 14: Line 15:


local m_game = mw.loadData('Module:Game')
local m_game = mw.loadData('Module:Game')
-- Lazy loading
local f_item_link -- require('Module:Item link').item_link


-- The cfg table contains all localisable strings and configuration, to make it
-- The cfg table contains all localisable strings and configuration, to make it
Line 30: Line 28:
local h = {}
local h = {}


-- Lazy loading for Module:Item link
function h.genericize_stat_text(str)
function h.item_link(args)
    if not f_item_link then
        f_item_link = require('Module:Item link').item_link
    end
    return f_item_link(args)
end
 
function h.header(str)
     --[[
     --[[
     This function replaces specific numbers with a generic #.
     This function replaces individual numbers and ranges of numbers with a number sign (#).
     ]]
     ]]


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


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


    -- Separator between fields:
local item_mods = {}
    args.delimiter = args.delimiter or ', '


     return function(tpl_args, tr, data, fields)
item_mods.sections = {
        local values = {}
     {
         local fmt_values = {}
        heading = i18n.item_mods.standard_mods,
 
        where = function (item_data)
         for index, field in ipairs(fields) do
            return [[
             local value = {
                mods.id NOT LIKE "%%Royale%%"
                min=data[field],
            ]]
                max=data[field],
        end,
                base=data[field],
    },
            }
    {
             if value.min then
         show = function (item_data)
                values[#values+1] = value.max
            return item_data.can_have_influences
                local opts = args.options[index] or {}
         end,
                -- Global colour is set, no overrides.
        tags = function (item_data)
                if args.color ~= nil or opts.color == nil then
             return {item_data.addon_tags.shaper}
                    opts.no_color = true
        end,
                end
        heading = i18n.item_mods.shaper_mods,
                fmt_values[#fmt_values+1] = m_util.html.format_value(nil, value, opts)
    },
             end
    {
         end
        show = function (item_data)
 
             return item_data.can_have_influences
         if #values == 0 then
        end,
             tr
        tags = function (item_data)
                :wikitext(m_util.html.td.na())
            return {item_data.addon_tags.elder}
         else
        end,
            local td = tr:tag('td')
        heading = i18n.item_mods.elder_mods,
             td
    },
                :attr('data-sort-value', table.concat(values, args.delimiter))
    {
                :wikitext(table.concat(fmt_values, args.delimiter))
        show = function (item_data)
             if args.color then
            return item_data.can_have_influences
                td:attr('class', 'tc -' .. args.color)
        end,
            end
        tags = function (item_data)
         end
            return {item_data.addon_tags.crusader}
     end
        end,
end
        heading = i18n.item_mods.crusader_mods,
 
    },
function h.get_spawn_chance(args)
    {
    --[[
        show = function (item_data)
    Calculates the spawn chance of a set of mods that all have a
             return item_data.can_have_influences
    spawn weight.
         end,
 
         tags = function (item_data)
     ]]
             return {item_data.addon_tags.eyrie}
 
        end,
     -- Get the table:
        heading = i18n.item_mods.redeemer_mods,
    local tbl = args['tbl']
    },
 
    {
    -- Probabilities affecting the result besides the spawn weight:
        show = function (item_data)
    local chance_multiplier = tonumber(args['chance_multiplier']) or 1
            return item_data.can_have_influences
 
         end,
    -- Total number of outcomes.
        tags = function (item_data)
    local N = 0
             return {item_data.addon_tags.basilisk}
    for i,_ in ipairs(tbl) do
        end,
        N = N + tbl[i]['spawn_weights.weight']
        heading = i18n.item_mods.hunter_mods,
    end
    },
 
    {
     for i,_ in ipairs(tbl) do
        show = function (item_data)
        -- Number of ways it can happen:
             return item_data.can_have_influences
         local n = tbl[i]['spawn_weights.weight']
        end,
 
        tags = function (item_data)
         -- Truncated value:
            return {item_data.addon_tags.adjudicator}
         tbl[i]['spawn_weights.chance'] = string.format(
        end,
             "%0.2f%%",
         heading = i18n.item_mods.warlord_mods,
             n/N * chance_multiplier*100
     },
         )
    {
     end
        show = function (item_data)
 
            return item_data.can_have_veiled_mods
    return tbl
        end,
end
        heading = i18n.item_mods.veiled_mods,
        domain = cfg.mod_domains.unveiled,
     },
     {
        show = function (item_data)
            return item_data.can_be_corrupted
        end,
        heading = i18n.item_mods.corruption_implicit_mods,
        generation_types = {
            {
                id = cfg.mod_generation_types.corrupted,
            },
        },
     },
    {
         show = function (item_data)
            return cfg.eldritch_implicit_item_classes[item_data.class_id] or false
        end,
         heading = i18n.item_mods.eldritch_implicit_mods,
         generation_types = {
            {
                id = cfg.mod_generation_types.searing_exarch,
                heading = i18n.item_mods.searing_exarch,
            },
             {
                id = cfg.mod_generation_types.eater_of_worlds,
                heading = i18n.item_mods.eater_of_worlds,
             },
         },
     },
}


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
Line 153: Line 166:
                 5 to Damage
                 5 to Damage
     * Add a where condition that somehow filters out mods that obviously
     * Add a where condition that somehow filters out mods that obviously
       wont match with the item. spawn_weights.weight>0 isn't enough due
       wont match with the item. mod_spawn_weights.value>0 isn't enough due
       to possible edge cases.
       to possible edge cases.


Line 193: Line 206:
     ]]
     ]]


     -- Format the cargo query:
     -- Support legacy args
     local where
    tpl_args.item_name = tpl_args.item_name or tpl_args.item
     for _,v in ipairs({
 
             {tpl_args.page, 'items._pageName = "%s"'},
     -- Query item
             {tpl_args.item, 'items.name = "%s"'},
     local item_data = m_item_util.query_item(tpl_args, {
     }) do
        fields = {
         if v[1] ~= nil then
             'items.name=name',
             where = string.format(v[2], v[1])
            'items.tags=tags',
            break
             'items.inventory_icon=inventory_icon',
            'items.html=html',
        }
     })
    if item_data.error then
         if not m_util.cast.boolean(tpl_args.nocat) then
             return item_data.error:get_html() .. item_data.error:get_category()
         end
         end
        return item_data.error:get_html()
     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 value
    local class_to_domain = {
        ['LifeFlask']=2,
        ['ManaFlask']=2,
        ['HybridFlask']=2,
        ['UtilityFlask']=2,
        ['UtilityFlaskCritical']=2,
        ['Map']=5,
        ['Jewel']=10,
        ['Leaguestone']=12,
        ['AbyssJewel']=13,
        ['HeistEquipmentUtility']=23,
        ['HeistEquipmentTool']=23,
        ['HeistEquipmentWeapon']=23,
        ['HeistEquipmentReward']=23,
        ['Trinket']=24,
        ['AtlasRegionUpgradeItem']=25,
        ['SentinelDrone']=30,
    }
     -- Get the domain, if it's not defined in the table assume it's
     -- Get the domain, if it's not defined in the table assume it's
     -- in the item domain.
     -- in the item domain.
     item_info['items.domain'] = tonumber(tpl_args.domain) or class_to_domain[item_info['items.class_id']] or 1
     item_data.domain = tonumber(tpl_args.domain) or cfg.mod_domains_by_item_class[item_data.class_id] or cfg.mod_domains.item


    -- Convert the mod domain number to understandable text:
     -- Format item tags
    item_info['items.domain_text'] = m_game.constants.mod.domains[item_info['items.domain']]['short_lower']
     tpl_args.item_tags = m_util.cast.table(tpl_args.item_tags or item_data.tags)
 
    if tpl_args.extra_item_tags then
     -- Format item tags:
         local extra_tags = m_util.cast.table(tpl_args.extra_item_tags)
     tpl_args.item_tags = m_util.string.split_args(
        for _, v in ipairs(extra_tags) do
        tpl_args.item_tags or item_info['items.tags'],
            tpl_args.item_tags[#tpl_args.item_tags+1] = v
         {sep=',%s*'}
        end
    )
    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
     end


     -- Format extra fields:
     -- Determine whether the item can have influences
     local extra_fields = m_util.string.split_args(tpl_args.extra_fields, {sep=',%s*'})
     item_data.can_have_influences = m_util.cast.boolean(m_game.constants.item.classes[item_data.class_id].can_have_influences)


    -- Determine whether the item can be corrupted
    item_data.can_be_corrupted = m_util.cast.boolean(m_game.constants.item.classes[item_data.class_id].can_be_corrupted)


     -- Get tags that are appended to special items:
     -- Determine whether the item can have veiled mods
     local tags_cfg = m_game.constants.item.classes[item_info['items.class_id']].tags or {}
     item_data.can_have_veiled_mods = m_util.cast.boolean(m_game.constants.item.classes[item_data.class_id].can_have_veiled_mods)


     -- Create drop down lists in these sections and query in these
     -- Get tags that are appended to influenced items
     -- generation types.
     item_data.addon_tags = m_game.constants.item.classes[item_data.class_id].tags or {}
    local section = {
        {
            header = i18n.item_mod_list.prefix,
            where = function(tpl_args, 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 > '')
                        ]],
                        item_tag,
                        1,
                        item_info['items.domain']
                    )
                end


                return table.concat(where, ' OR ')
    -- Populate mods data
            end,
    for _, section in ipairs(item_mods.sections) do
        },
        -- Default generation types
        {
        if type(section.generation_types) ~= 'table' then
            header = i18n.item_mod_list.suffix,
             section.generation_types = {
            where = function(tpl_args, value)
                 {
                local where = {}
                     id = cfg.mod_generation_types.prefix,
                for _, item_tag in ipairs(tpl_args.item_tags) do
                     heading = i18n.item_mods.prefixes,
                    where[#where+1] = string.format(
                     no_results = i18n.item_mods.prefixes_no_results
                        [[
                 },
                            (spawn_weights.tag="%s"
                {
                            AND mods.generation_type=%s
                     id = cfg.mod_generation_types.suffix,
                            AND mods.domain=%s
                     heading = i18n.item_mods.suffixes,
                            AND mods.stat_text > '')
                     no_results = i18n.item_mods.suffixes_no_results
                        ]],
                 }
                        item_tag,
             }
                        2,
         end
                        item_info['items.domain']
                    )
                end
 
                return table.concat(where, ' OR ')
             end,
        },
        {
            tags = {tags_cfg.elder},
            header = i18n.item_mod_list.elder_prefix,
            where = function(tpl_args, value)
                 return string.format(
                     [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.elder,
                    1,
                     item_info['items.domain']
                )
            end,
        },
        {
            tags = {tags_cfg.elder},
            header = i18n.item_mod_list.elder_suffix,
            where = function(tpl_args, value)
                return string.format(
                     [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.elder,
                    2,
                    item_info['items.domain']
                 )
            end,
        },
        {
            tags = {tags_cfg.shaper},
            header = i18n.item_mod_list.shaper_prefix,
            where = function(tpl_args, value)
                return string.format(
                     [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                     tags_cfg.shaper,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {tags_cfg.shaper},
            header = i18n.item_mod_list.shaper_suffix,
            where = function(tpl_args, value)
                return string.format(
                     [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.shaper,
                    2,
                    item_info['items.domain']
                 )
             end,
        },
         {
            header = i18n.item_mod_list.delve_prefix,
            where = function(tpl_args, 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 > '')
                        ]],
                        item_tag,
                        1,
                        16
                    )
                end


                return table.concat(where, ' OR ')
         -- Show the section? Default: Show
            end,
         local show = section.show ~= false
        },
        if type(section.show) == 'function' then
        {
             show = section.show(item_data)
            header = i18n.item_mod_list.delve_suffix,
            where = function(tpl_args, 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 > '')
                        ]],
                        item_tag,
                        2,
                        16
                    )
                end
 
                return table.concat(where, ' OR ')
            end,
        },
        {
            tags = {tags_cfg.crusader},
            header = i18n.item_mod_list.crusader_prefix,
            where = function(tpl_args, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.crusader,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {tags_cfg.crusader},
            header = i18n.item_mod_list.crusader_suffix,
            where = function(tpl_args, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.crusader,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {tags_cfg.eyrie},
            header = i18n.item_mod_list.eyrie_prefix,
            where = function(tpl_args, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.eyrie,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {tags_cfg.eyrie},
            header = i18n.item_mod_list.eyrie_suffix,
            where = function(tpl_args, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.eyrie,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {tags_cfg.basilisk},
            header = i18n.item_mod_list.basilisk_prefix,
            where = function(tpl_args, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.basilisk,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {tags_cfg.basilisk},
            header = i18n.item_mod_list.basilisk_suffix,
            where = function(tpl_args, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.basilisk,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {tags_cfg.adjudicator},
            header = i18n.item_mod_list.adjudicator_prefix,
            where = function(tpl_args, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.adjudicator,
                    1,
                    item_info['items.domain']
                )
            end,
        },
         {
            tags = {tags_cfg.adjudicator},
            header = i18n.item_mod_list.adjudicator_suffix,
            where = function(tpl_args, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text > '')
                    ]],
                    tags_cfg.adjudicator,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            header = i18n.item_mod_list.veiled_prefix,
            where = function(tpl_args, 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 > ''
                            AND mods.id LIKE "%%Master%%")
                        ]],
                        item_tag,
                        1,
                        9
                    )
                end
 
                return table.concat(where, ' OR ')
            end,
        },
        {
            header = i18n.item_mod_list.veiled_suffix,
            where = function(tpl_args, 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 > ''
                            AND mods.id LIKE "%%Master%%")
                        ]],
                        item_tag,
                        2,
                        9
                    )
                end
 
                return table.concat(where, ' OR ')
            end,
        },
        {
            header = i18n.item_mod_list.corrupted,
            where = function(tpl_args, 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 > '')
                        ]],
                        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.item_mod_list.enchant,
            -- where = function(tpl_args, 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 > '')
                        -- ]],
                        -- 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
         end
         for _, header in ipairs({sctn['header'], 'all'}) do
         if show then
             if mod_group_counter[header][adj] == nil then
            -- Get item tags
                 mod_group_counter[header][adj] = {}
            local section_tags = type(section.tags) == 'function' and section.tags(item_data) or section.tags or tpl_args.item_tags
             if type(section_tags) ~= 'table' or #section_tags == 0 then
                 error('No tags.')
             end
             end
        end


        local continue = true
             -- Build mods data for each generation type
        local current_tags
             section.mods_data = {}
        if sctn['tags'] then
             for _, gen_type in ipairs(section.generation_types) do
             -- some item classes do not have shaper/elder items, so the table
                 section.mods_data[gen_type.id] = {}
             -- 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
                -- Query mods
            -- Cargo preparation:
                local where = {
            local q_fields = {
                    string.format('mods.domain = %s', section.domain or item_data.domain),
                'mods._pageName',
                    string.format('mods.generation_type = %s', gen_type.id),
                'mods.name',
                    'mods.stat_text IS NOT NULL',
                'mods.id',
                    string.format('mod_spawn_weights.tag IN ("%s")', table.concat(section_tags, '","')),
                'mods.required_level',
                 }
                 'mods.generation_type',
                if section.where then
                'mods.domain',
                    where[#where+1] = section.where(item_data)
                'mods.mod_group',
                end
                'mods.mod_type',
                local results = m_cargo.query(
                'mods.stat_text',
                    {
                'mods.stat_text_raw',
                        'mods',
                'mods.tags',
                        'mod_stats',
                'mod_stats.id',
                        'mod_spawn_weights',
                'spawn_weights.tag',
                    },
                'spawn_weights.weight',
                    {
                'spawn_weights.ordinal',
                        'mods._pageID',
                'spawn_weights._pageName'
                        'mods._pageName',
            }
                        'mods.name',
            for i, v in ipairs(extra_fields) do
                        'mods.id',
                q_fields[#q_fields+1] = v
                        'mods.required_level',
            end
                        'mods.generation_type',
 
                        'mods.domain',
            -- Query mods and map the results to the pagename:
                        'mods.mod_groups',
            local results = m_cargo.map_results_to_id{
                        'mods.mod_type',
                results=m_cargo.query(
                        'mods.stat_text',
                    {'mods', 'spawn_weights', 'mod_stats'},
                        'mods.stat_text_raw',
                     q_fields,
                        'mods.tags',
                        'mod_stats.id',
                        'mod_spawn_weights.tag',
                        'mod_spawn_weights.value',
                        'mod_spawn_weights.ordinal',
                     },
                     {
                     {
                         join = [[
                         join = [[
                             mods._pageName=spawn_weights._pageName,
                             mods._pageID=mod_spawn_weights._pageID,
                             mods._pageName=mod_stats._pageName
                             mods._pageID=mod_stats._pageID
                        ]],
                        where = sctn['where'](tpl_args, value),
                        groupBy = [[
                            mods._pageName,
                            spawn_weights.tag,
                            spawn_weights.weight
                         ]],
                         ]],
                        where = table.concat(where, ' AND '),
                        groupBy = 'mods._pageID',
                        having = 'mod_spawn_weights.value > 0',
                         orderBy = [[
                         orderBy = [[
                             mods.generation_type,
                             mods.mod_groups ASC,
                            mods.mod_group,
                             mods.mod_type ASC,
                             mods.mod_type,
                             mods.required_level ASC,
                             mods.required_level,
                             mod_spawn_weights.ordinal ASC
                             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
                -- Group results
                        -- interesting information:
                if #results > 0 then
                        if tag_match_add then
                    for _, row in ipairs(results) do
 
                        row['mods.mod_groups'] = m_util.cast.table(row['mods.mod_groups'])
                            -- Assume that the mod is global then go through
                        row['mods.tags'] = m_util.cast.table(row['mods.tags'])
                            -- all the stat ids and check if any of the
                        if #row['mods.mod_groups'] > 0 then
                            -- stats are local:
                             for _, group in ipairs(row['mods.mod_groups']) do
                            local mod_scope = 'Global'
                                 section.mods_data[gen_type.id][group] = section.mods_data[gen_type.id][group] or {}
                            for _, vv in ipairs(results[id]) do
                                 section.mods_data[gen_type.id][group][row['mods.mod_type']] = section.mods_data[gen_type.id][group][row['mods.mod_type']] or {}
                                if vv['mod_stats.id']:find('.*local.*') ~= nil then
                                 table.insert(section.mods_data[gen_type.id][group][row['mods.mod_type']], row)
                                    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
                         end
                         end
                     until tag_match_stop
                     end
                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
                 end
            end


                 extra_fieldss[sctn['header']] = extra_fields
            if tpl_args.debug then
                 mw.logObject(section.mods_data)
             end
             end
         end
         end
     end
     end


 
     -- Build html output
     --
     local html = mw.html.create()
     -- Display the item mods
     for _, section in ipairs(item_mods.sections) do
     --
        local section_wrapper
 
        local empty = true -- Section is empty
    -- Introductory text:
        if section.mods_data then
    local out = {}
            section_wrapper = mw.html.create('div')
    out[#out+1] = string.format(
                    :addClass('mod-compat__section')
        '==%s== \n',
                    :tag('h3')
        tpl_args['header'] or table.concat(tpl_args.item_tags, ', ')
                        :addClass('mod-compat__section-heading')
    )
                        :wikitext(section.heading)
    local expand_button = string.format(
                        :done()
        '<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>',
            for _, gen_type in ipairs(section.generation_types) do
        i18n.item_mod_list.collapse_all,
                local gentype_wrapper = section_wrapper
        i18n.item_mod_list.expand_all
                    :tag('div')
    )
                        :addClass('mod-compat__gentype')
    out[#out+1] = expand_button
                local gentype_header = gentype_wrapper
    out[#out+1] = string.format('%s %s.<br><br><br>',
                    :tag('div')
        i18n.item_mod_list.table_intro,
                        :addClass('mod-compat__gentype-header')
        h.item_link{
                        :tag('span')
            page=item_info['items._pageName'],
                            :addClass('mod-compat__gentype-heading')
            name=item_info['items.name'],
                            :wikitext(gen_type.heading or i18n.item_mods.modifiers)
            inventory_icon=item_info['items.inventory_icon'] or '',
                            :done()
            html=item_info['items.html'] or '',
                if type(section.mods_data[gen_type.id]) == 'table' and m_util.table.length(section.mods_data[gen_type.id]) > 0 then
            skip_query=true
                    empty = false
        }
                    gentype_header
    )
                        :tag('span')
 
                            :addClass('accordion__controls mw-editsection-like')
    -- Loop through the sections:
                    for gkey, gval in pairs(section.mods_data[gen_type.id]) do
    for _, sctn in ipairs(section) do
                        local group_wrapper = gentype_wrapper
        local extra_fields = extra_fieldss[sctn['header']]
                            :tag('div')
        local adj = 'explicit'
                                :addClass('mod-compat__group')
        if sctn['is_implicit'] then
                                :tag('div')
            adj = 'implicit'
                                    :addClass('mod-compat__group-label')
        end
                                    :wikitext( string.format('%s %s', i18n.item_mods.group, gkey) )
 
                                    :done()
        -- Create html container:
                        local mod_type_list = group_wrapper
        local container = mw.html.create('div')
                            :tag('dl')
            :attr('style', 'vertical-align:top; display:inline-block;')
                                :addClass('mod-compat__type-list accordion')
 
                        for tkey, tval in pairs(gval) do
        -- Create the drop down table with <table></table>:
                            local summary_text = tval[1]['mods.stat_text_raw']
        local headers = container
                            if m_util.table.length(tval) > 1 then
        if #item_mods[sctn['header']] > 0 then
                                summary_text = h.genericize_stat_text(summary_text)
            headers
                            end
                :tag('h3')
                            local mod_type_heading = mod_type_list
                    :wikitext(string.format('%s', sctn['header']))
                                :tag('dt')
                    :done()
                                    :addClass('mod-compat__type-summary accordion__toggle')
                :done()
                                    :wikitext( m_util.html.poe_color('mod', summary_text) )
        end
                            local mod_type_body = mod_type_list
 
                                :tag('dd')
        local total_mod_groups = 0
                                    :addClass('mod-compat__type-details accordion__panel')
        for _ in pairs(mod_group_counter[sctn['header']][adj]) do
                            local mod_table = mod_type_body
            total_mod_groups = 1+total_mod_groups
                                :tag('table')
        end
                                    :addClass('wikitable modifier-table')
 
                                    :tag('tr')
        -- Loop through and add all matching mods to the <table>.
                                        :tag('th')
        local tbl, last_group, last_type, table_index
                                            :wikitext(i18n.item_mods.modifier)
        for _, rows in ipairs(item_mods[sctn['header']]) do
                                            :done()
 
                                        :tag('th')
            -- If the last mod group is different to the current
                                            :wikitext(i18n.item_mods.required_level)
            -- mod group then assume the mod isn't related and start
                                            :done()
            -- a new drop down list, if there's only one mod group
                                        :tag('th')
            -- then use mod type instead:
                                            :wikitext(i18n.item_mods.stats)
            if rows['mods.mod_group'] ~= last_group or (total_mod_groups == 1 and rows['mods.mod_type'] ~= last_type) then
                                            :done()
                -- Check through all the mods and see if there are
                                        :tag('th')
                -- multiple mod types within the same mod group:
                                            :wikitext(i18n.item_mods.tags)
                local count = {}
                                            :done()
                for _, n in ipairs(item_mods[sctn['header']]) do
                                        :done()
 
                            for _, mod in ipairs(tval) do
                    -- If the mod has the same mod group, then add
                                local name = mod['mods.name'] or mod['mods.mod_type'] or mod['mods.id']
                    -- the mod type to the counter. Only unique mod
                                local tag_list = mw.html.create('ul')
                    -- types matter so the number is just a dummy
                                    :addClass('modifier-table__tag-list')
                    -- value:
                                for _, tag in ipairs(mod['mods.tags']) do
                    if n['mods.mod_group'] == rows['mods.mod_group'] then
                                    tag_list
                        count[n['mods.mod_type']] = 1
                                        :tag('li')
                                            :addClass('modifier-table__tag')
                                            :wikitext(tag)
                                end
                                mod_table
                                    :tag('tr')
                                        :tag('td')
                                            :wikitext( m_util.html.wikilink(mod['mods._pageName'], name) )
                                            :done()
                                        :tag('td')
                                            :wikitext(mod['mods.required_level'])
                                            :done()
                                        :tag('td')
                                            :wikitext( m_util.html.poe_color('mod', mod['mods.stat_text_raw']) )
                                            :done()
                                        :tag('td')
                                            :node(tag_list)
                                            :done()
                            end
                        end
                     end
                     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
                local tbl_caption
                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.item_mod_list.mod_group,
                                rows['mods.mod_group']
                            )
                        ) or ''
                    )
                 else
                 else
                     tbl_caption = string.format(
                     gentype_wrapper
                         '%s (%s)',
                         :wikitext(gen_type.no_results)
                        m_util.html.poe_color(
                            'mod',
                            h.header(rows['mods.stat_text_raw'])
                        ) or '',
                        rows['mods.scope']
                    )
                 end
                 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
             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)
            end
            -- Add a table row with the interesting properties that
            -- modifier has:
            if table_index ~= nil then
                tbl = container:tag('table')
                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()
            end
            -- Save the last mod group for later comparison:
            last_group = rows['mods.mod_group']
            last_type = rows['mods.mod_type']
         end
         end
         out[#out+1] = tostring(container)
         if not empty then
            html:node(section_wrapper)
        end
     end
     end
 
     return tostring(html)
    -- Outro text:
    out[#out+1] = '<br>'
    out[#out+1] = m_util.html.poe_color(
        'normal',
        string.format('[[#Top|%s]]', i18n.item_mod_list.back_to_top)
    )
 
     return table.concat(out,'')
end
end


Line 1,092: Line 476:
--  
--  
p.item_modifier_compatibility = m_util.misc.invoker_factory(_item_modifier_compatibility, {
p.item_modifier_compatibility = m_util.misc.invoker_factory(_item_modifier_compatibility, {
     wrappers = cfg.wrappers.item_mod_list,
     wrappers = cfg.wrappers.item_mods,
})
})


return p
return p

Latest revision as of 19:01, 15 December 2022

Module documentation[view] [edit] [history] [purge]


Implements {{Item modifier compatibility}}.

-------------------------------------------------------------------------------
-- 
--                           Module:Modifier compatibility
-- 
-- This module implements Template:Item modifier compatibility
-------------------------------------------------------------------------------

require('Module:No globals')
local m_util = require('Module:Util')
local m_item_util = require('Module:Item util')
local m_cargo = require('Module:Cargo')

-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Modifier compatibility')

local m_game = mw.loadData('Module:Game')

-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Modifier compatibility/config/sandbox') or mw.loadData('Module:Modifier compatibility/config')

local i18n = cfg.i18n

-- ----------------------------------------------------------------------------
-- Helper functions
-- ----------------------------------------------------------------------------

local h = {}

function h.genericize_stat_text(str)
    --[[
    This function replaces individual numbers and ranges of numbers with a number sign (#).
    ]]

    str = string.gsub(str, '%(%d+%.?%d*%-%d+%.?%d*%)', '#')
    str = string.gsub(str, '%d+%.?%d*', '#')
    return str
end

-- ----------------------------------------------------------------------------
-- Data and configuration for item mods
-- ----------------------------------------------------------------------------

local item_mods = {}

item_mods.sections = {
    {
        heading = i18n.item_mods.standard_mods,
        where = function (item_data)
            return [[
                mods.id NOT LIKE "%%Royale%%"
            ]]
        end,
    },
    {
        show = function (item_data)
            return item_data.can_have_influences
        end,
        tags = function (item_data)
            return {item_data.addon_tags.shaper}
        end,
        heading = i18n.item_mods.shaper_mods,
    },
    {
        show = function (item_data)
            return item_data.can_have_influences
        end,
        tags = function (item_data)
            return {item_data.addon_tags.elder}
        end,
        heading = i18n.item_mods.elder_mods,
    },
    {
        show = function (item_data)
            return item_data.can_have_influences
        end,
        tags = function (item_data)
            return {item_data.addon_tags.crusader}
        end,
        heading = i18n.item_mods.crusader_mods,
    },
    {
        show = function (item_data)
            return item_data.can_have_influences
        end,
        tags = function (item_data)
            return {item_data.addon_tags.eyrie}
        end,
        heading = i18n.item_mods.redeemer_mods,
    },
    {
        show = function (item_data)
            return item_data.can_have_influences
        end,
        tags = function (item_data)
            return {item_data.addon_tags.basilisk}
        end,
        heading = i18n.item_mods.hunter_mods,
    },
    {
        show = function (item_data)
            return item_data.can_have_influences
        end,
        tags = function (item_data)
            return {item_data.addon_tags.adjudicator}
        end,
        heading = i18n.item_mods.warlord_mods,
    },
    {
        show = function (item_data)
            return item_data.can_have_veiled_mods
        end,
        heading = i18n.item_mods.veiled_mods,
        domain = cfg.mod_domains.unveiled,
    },
    {
        show = function (item_data)
            return item_data.can_be_corrupted
        end,
        heading = i18n.item_mods.corruption_implicit_mods,
        generation_types = {
            {
                id = cfg.mod_generation_types.corrupted,
            },
        },
    },
    {
        show = function (item_data)
            return cfg.eldritch_implicit_item_classes[item_data.class_id] or false
        end,
        heading = i18n.item_mods.eldritch_implicit_mods,
        generation_types = {
            {
                id = cfg.mod_generation_types.searing_exarch,
                heading = i18n.item_mods.searing_exarch,
            },
            {
                id = cfg.mod_generation_types.eater_of_worlds,
                heading = i18n.item_mods.eater_of_worlds,
            },
        },
    },
}

-- ----------------------------------------------------------------------------
-- Main functions
-- ----------------------------------------------------------------------------

local function _item_modifier_compatibility(tpl_args)
    --[[
    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. mod_spawn_weights.value>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',
    }

    ]]

    -- Support legacy args
    tpl_args.item_name = tpl_args.item_name or tpl_args.item

    -- Query item
    local item_data = m_item_util.query_item(tpl_args, {
        fields = {
            'items.name=name',
            'items.tags=tags',
            'items.inventory_icon=inventory_icon',
            'items.html=html',
        }
    })
    if item_data.error then
        if not m_util.cast.boolean(tpl_args.nocat) then
            return item_data.error:get_html() .. item_data.error:get_category()
        end
        return item_data.error:get_html()
    end

    -- Get the domain, if it's not defined in the table assume it's
    -- in the item domain.
    item_data.domain = tonumber(tpl_args.domain) or cfg.mod_domains_by_item_class[item_data.class_id] or cfg.mod_domains.item

    -- Format item tags
    tpl_args.item_tags = m_util.cast.table(tpl_args.item_tags or item_data.tags)
    if tpl_args.extra_item_tags then
        local extra_tags = m_util.cast.table(tpl_args.extra_item_tags)
        for _, v in ipairs(extra_tags) do
            tpl_args.item_tags[#tpl_args.item_tags+1] = v
        end
    end

    -- Determine whether the item can have influences
    item_data.can_have_influences = m_util.cast.boolean(m_game.constants.item.classes[item_data.class_id].can_have_influences)

    -- Determine whether the item can be corrupted
    item_data.can_be_corrupted = m_util.cast.boolean(m_game.constants.item.classes[item_data.class_id].can_be_corrupted)

    -- Determine whether the item can have veiled mods
    item_data.can_have_veiled_mods = m_util.cast.boolean(m_game.constants.item.classes[item_data.class_id].can_have_veiled_mods)

    -- Get tags that are appended to influenced items
    item_data.addon_tags = m_game.constants.item.classes[item_data.class_id].tags or {}

    -- Populate mods data
    for _, section in ipairs(item_mods.sections) do
        -- Default generation types
        if type(section.generation_types) ~= 'table' then
            section.generation_types = {
                {
                    id = cfg.mod_generation_types.prefix,
                    heading = i18n.item_mods.prefixes,
                    no_results = i18n.item_mods.prefixes_no_results
                },
                {
                    id = cfg.mod_generation_types.suffix,
                    heading = i18n.item_mods.suffixes,
                    no_results = i18n.item_mods.suffixes_no_results
                }
            }
        end

        -- Show the section? Default: Show
        local show = section.show ~= false
        if type(section.show) == 'function' then
            show = section.show(item_data)
        end
        if show then
            -- Get item tags
            local section_tags = type(section.tags) == 'function' and section.tags(item_data) or section.tags or tpl_args.item_tags
            if type(section_tags) ~= 'table' or #section_tags == 0 then
                error('No tags.')
            end

            -- Build mods data for each generation type
            section.mods_data = {}
            for _, gen_type in ipairs(section.generation_types) do
                section.mods_data[gen_type.id] = {}

                -- Query mods
                local where = {
                    string.format('mods.domain = %s', section.domain or item_data.domain),
                    string.format('mods.generation_type = %s', gen_type.id),
                    'mods.stat_text IS NOT NULL',
                    string.format('mod_spawn_weights.tag IN ("%s")', table.concat(section_tags, '","')),
                }
                if section.where then
                    where[#where+1] = section.where(item_data)
                end
                local results = m_cargo.query(
                    {
                        'mods',
                        'mod_stats',
                        'mod_spawn_weights',
                    },
                    {
                        'mods._pageID',
                        'mods._pageName',
                        'mods.name',
                        'mods.id',
                        'mods.required_level',
                        'mods.generation_type',
                        'mods.domain',
                        'mods.mod_groups',
                        'mods.mod_type',
                        'mods.stat_text',
                        'mods.stat_text_raw',
                        'mods.tags',
                        'mod_stats.id',
                        'mod_spawn_weights.tag',
                        'mod_spawn_weights.value',
                        'mod_spawn_weights.ordinal',
                    },
                    {
                        join = [[
                            mods._pageID=mod_spawn_weights._pageID,
                            mods._pageID=mod_stats._pageID
                        ]],
                        where = table.concat(where, ' AND '),
                        groupBy = 'mods._pageID',
                        having = 'mod_spawn_weights.value > 0',
                        orderBy = [[
                            mods.mod_groups ASC,
                            mods.mod_type ASC,
                            mods.required_level ASC,
                            mod_spawn_weights.ordinal ASC
                        ]],
                    }
                )

                -- Group results
                if #results > 0 then
                    for _, row in ipairs(results) do
                        row['mods.mod_groups'] = m_util.cast.table(row['mods.mod_groups'])
                        row['mods.tags'] = m_util.cast.table(row['mods.tags'])
                        if #row['mods.mod_groups'] > 0 then
                            for _, group in ipairs(row['mods.mod_groups']) do
                                section.mods_data[gen_type.id][group] = section.mods_data[gen_type.id][group] or {}
                                section.mods_data[gen_type.id][group][row['mods.mod_type']] = section.mods_data[gen_type.id][group][row['mods.mod_type']] or {}
                                table.insert(section.mods_data[gen_type.id][group][row['mods.mod_type']], row)
                            end
                        end
                    end
                end
            end

            if tpl_args.debug then
                mw.logObject(section.mods_data)
            end
        end
    end

    -- Build html output
    local html = mw.html.create()
    for _, section in ipairs(item_mods.sections) do
        local section_wrapper
        local empty = true -- Section is empty
        if section.mods_data then
            section_wrapper = mw.html.create('div')
                    :addClass('mod-compat__section')
                    :tag('h3')
                        :addClass('mod-compat__section-heading')
                        :wikitext(section.heading)
                        :done()
            for _, gen_type in ipairs(section.generation_types) do
                local gentype_wrapper = section_wrapper
                    :tag('div')
                        :addClass('mod-compat__gentype')
                local gentype_header = gentype_wrapper
                    :tag('div')
                        :addClass('mod-compat__gentype-header')
                        :tag('span')
                            :addClass('mod-compat__gentype-heading')
                            :wikitext(gen_type.heading or i18n.item_mods.modifiers)
                            :done()
                if type(section.mods_data[gen_type.id]) == 'table' and m_util.table.length(section.mods_data[gen_type.id]) > 0 then
                    empty = false
                    gentype_header
                        :tag('span')
                            :addClass('accordion__controls mw-editsection-like')
                    for gkey, gval in pairs(section.mods_data[gen_type.id]) do
                        local group_wrapper = gentype_wrapper
                            :tag('div')
                                :addClass('mod-compat__group')
                                :tag('div')
                                    :addClass('mod-compat__group-label')
                                    :wikitext( string.format('%s %s', i18n.item_mods.group, gkey) )
                                    :done()
                        local mod_type_list = group_wrapper
                            :tag('dl')
                                :addClass('mod-compat__type-list accordion')
                        for tkey, tval in pairs(gval) do
                            local summary_text = tval[1]['mods.stat_text_raw']
                            if m_util.table.length(tval) > 1 then
                                summary_text = h.genericize_stat_text(summary_text)
                            end
                            local mod_type_heading = mod_type_list
                                :tag('dt')
                                    :addClass('mod-compat__type-summary accordion__toggle')
                                    :wikitext( m_util.html.poe_color('mod', summary_text) )
                            local mod_type_body = mod_type_list
                                :tag('dd')
                                    :addClass('mod-compat__type-details accordion__panel')
                            local mod_table = mod_type_body
                                :tag('table')
                                    :addClass('wikitable modifier-table')
                                    :tag('tr')
                                        :tag('th')
                                            :wikitext(i18n.item_mods.modifier)
                                            :done()
                                        :tag('th')
                                            :wikitext(i18n.item_mods.required_level)
                                            :done()
                                        :tag('th')
                                            :wikitext(i18n.item_mods.stats)
                                            :done()
                                        :tag('th')
                                            :wikitext(i18n.item_mods.tags)
                                            :done()
                                        :done()
                            for _, mod in ipairs(tval) do
                                local name = mod['mods.name'] or mod['mods.mod_type'] or mod['mods.id']
                                local tag_list = mw.html.create('ul')
                                    :addClass('modifier-table__tag-list')
                                for _, tag in ipairs(mod['mods.tags']) do
                                    tag_list
                                        :tag('li')
                                            :addClass('modifier-table__tag')
                                            :wikitext(tag)
                                end
                                mod_table
                                    :tag('tr')
                                        :tag('td')
                                            :wikitext( m_util.html.wikilink(mod['mods._pageName'], name) )
                                            :done()
                                        :tag('td')
                                            :wikitext(mod['mods.required_level'])
                                            :done()
                                        :tag('td')
                                            :wikitext( m_util.html.poe_color('mod', mod['mods.stat_text_raw']) )
                                            :done()
                                        :tag('td')
                                            :node(tag_list)
                                            :done()
                            end
                        end
                    end
                else
                    gentype_wrapper
                        :wikitext(gen_type.no_results)
                end
            end
        end
        if not empty then
            html:node(section_wrapper)
        end
    end
    return tostring(html)
end

-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------

local p = {}

-- 
-- Template:Item modifier compatibility
-- 
p.item_modifier_compatibility = m_util.misc.invoker_factory(_item_modifier_compatibility, {
    wrappers = cfg.wrappers.item_mods,
})

return p