Module:Modifier compatibility/sandbox: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
 
(43 intermediate revisions by 2 users not shown)
Line 15: 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 31: 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.heading(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
 
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, 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, 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
end


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


local c = {}
local item_mods = {}


c.item_mods = {}
item_mods.sections = {
 
c.item_mods.sections = {
     {
     {
         heading = i18n.item_mods.prefix,
         heading = i18n.item_mods.standard_mods,
         where = function (item_data)
         where = function (item_data)
             return string.format(
             return [[
                [[
                mods.id NOT LIKE "%%Royale%%"
                    mods.domain = %s
             ]]
                    AND mods.generation_type = %s
                    AND mods.id NOT LIKE "%%Royale%%"
                ]],
                item_data.domain,
                cfg.mod_generation_types.prefix
             )
        end,
    },
    {
        heading = i18n.item_mods.suffix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain = %s
                    AND mods.generation_type = %s
                    AND mods.id NOT LIKE "%%Royale%%"
                ]],
                item_data.domain,
                cfg.mod_generation_types.suffix
            )
         end,
         end,
     },
     },
Line 142: Line 60:
             return {item_data.addon_tags.shaper}
             return {item_data.addon_tags.shaper}
         end,
         end,
         heading = i18n.item_mods.shaper_prefix,
         heading = i18n.item_mods.shaper_mods,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.prefix
            )
        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_suffix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.suffix
            )
        end,
    },
    {
        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_prefix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.prefix
            )
        end,
     },
     },
     {
     {
Line 199: Line 69:
             return {item_data.addon_tags.elder}
             return {item_data.addon_tags.elder}
         end,
         end,
         heading = i18n.item_mods.elder_suffix,
         heading = i18n.item_mods.elder_mods,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.suffix
            )
        end,
     },
     },
     {
     {
Line 218: Line 78:
             return {item_data.addon_tags.crusader}
             return {item_data.addon_tags.crusader}
         end,
         end,
         heading = i18n.item_mods.crusader_prefix,
         heading = i18n.item_mods.crusader_mods,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.prefix
            )
        end,
    },
    {
        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_suffix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.suffix
            )
        end,
    },
    {
        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.eyrie_prefix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.prefix
            )
        end,
     },
     },
     {
     {
Line 275: Line 87:
             return {item_data.addon_tags.eyrie}
             return {item_data.addon_tags.eyrie}
         end,
         end,
         heading = i18n.item_mods.eyrie_suffix,
         heading = i18n.item_mods.redeemer_mods,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.suffix
            )
        end,
     },
     },
     {
     {
Line 294: Line 96:
             return {item_data.addon_tags.basilisk}
             return {item_data.addon_tags.basilisk}
         end,
         end,
         heading = i18n.item_mods.basilisk_prefix,
         heading = i18n.item_mods.hunter_mods,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.prefix
            )
        end,
     },
     },
     {
     {
Line 311: Line 103:
         end,
         end,
         tags = function (item_data)
         tags = function (item_data)
             return {item_data.addon_tags.basilisk}
             return {item_data.addon_tags.adjudicator}
        end,
        heading = i18n.item_mods.basilisk_suffix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.suffix
            )
         end,
         end,
        heading = i18n.item_mods.warlord_mods,
     },
     },
     {
     {
         show = function (item_data)
         show = function (item_data)
             return item_data.can_have_influences
             return item_data.can_have_veiled_mods
        end,
        tags = function (item_data)
            return {item_data.addon_tags.adjudicator}
        end,
        heading = i18n.item_mods.adjudicator_prefix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.prefix
            )
         end,
         end,
        heading = i18n.item_mods.veiled_mods,
        domain = cfg.mod_domains.unveiled,
     },
     },
     {
     {
         show = function (item_data)
         show = function (item_data)
             return item_data.can_have_influences
             return item_data.can_be_corrupted
        end,
        tags = function (item_data)
            return {item_data.addon_tags.adjudicator}
        end,
        heading = i18n.item_mods.adjudicator_suffix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain=%s
                    AND mods.generation_type=%s
                ]],
                item_data.domain,
                cfg.mod_generation_types.suffix
            )
        end,
    },
    {
        heading = i18n.item_mods.delve_prefix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain = %s
                    AND mods.generation_type = %s
                ]],
                cfg.mod_domains.delve,
                cfg.mod_generation_types.prefix
            )
        end,
    },
    {
        heading = i18n.item_mods.delve_suffix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain = %s
                    AND mods.generation_type = %s
                ]],
                cfg.mod_domains.delve,
                cfg.mod_generation_types.suffix
            )
        end,
    },
    {
        heading = i18n.item_mods.veiled_prefix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain = %s
                    AND mods.generation_type = %s
                ]],
                cfg.mod_domains.unveiled,
                cfg.mod_generation_types.prefix
            )
        end,
    },
    {
        heading = i18n.item_mods.veiled_suffix,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain = %s
                    AND mods.generation_type = %s
                ]],
                cfg.mod_domains.unveiled,
                cfg.mod_generation_types.suffix
            )
         end,
         end,
        heading = i18n.item_mods.corruption_implicit_mods,
        generation_types = {
            {
                id = cfg.mod_generation_types.corrupted,
            },
        },
     },
     },
     {
     {
         show = function (item_data)
         show = function (item_data)
             return item_data.can_be_corrupted
             return cfg.eldritch_implicit_item_classes[item_data.class_id] or false
        end,
        heading = i18n.item_mods.corrupted,
        where = function (item_data)
            return string.format(
                [[
                    mods.domain = %s
                    AND mods.generation_type = %s
                ]],
                item_data.domain,
                cfg.mod_generation_types.corrupted
            )
         end,
         end,
         is_implicit = true,
         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 510: Line 219:
     })
     })
     if item_data.error then
     if item_data.error then
         return item_data.error
        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
     end


Line 516: Line 228:
     -- in the item domain.
     -- 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
     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:
    item_data.domain_text = m_game.constants.mod.domains[item_data.domain].short_lower


     -- Format item tags
     -- Format item tags
Line 534: Line 243:
     -- Determine whether the item can be corrupted
     -- 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)
     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
     -- Get tags that are appended to influenced items
     item_data.addon_tags = m_game.constants.item.classes[item_data.class_id].tags or {}
     item_data.addon_tags = m_game.constants.item.classes[item_data.class_id].tags or {}


     local item_mods = {}
     -- Populate mods data
    local item_mods2 = {}
    for _, section in ipairs(item_mods.sections) do
    local mod_group_counter = {}
        -- Default generation types
    mod_group_counter['all'] = {}
        if type(section.generation_types) ~= 'table' then
    local table_index_base = -1
            section.generation_types = {
    for _, section in ipairs(c.item_mods.sections) do
                {
                    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
         -- Show the section? Default: Show
         local show = section.show ~= false
         local show = section.show ~= false
Line 550: Line 274:
         end
         end
         if show then
         if show then
            item_mods[section.heading] = {}
             -- Get item tags
            item_mods2[section.heading] = {}
 
             -- Preallocate the mod group counter, implicit and explicit mods
            -- are counted separetely because they can spawn together:
            mod_group_counter[section.heading] = {}
            local adj = section.is_implicit and 'implicit' or 'explicit'
            for _, heading in ipairs({section.heading, 'all'}) do
                if mod_group_counter[heading][adj] == nil then
                    mod_group_counter[heading][adj] = {}
                end
            end
 
             local section_tags = type(section.tags) == 'function' and section.tags(item_data) or section.tags or tpl_args.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
             if type(section_tags) ~= 'table' or #section_tags == 0 then
                 error('No tags.')
                 error('No tags.')
             end
             end
             -- Query mods and map the results to the pagename
 
             local where = table.concat({
             -- Build mods data for each generation type
                section.where(item_data),
             section.mods_data = {}
                 'mods.stat_text IS NOT NULL',
            for _, gen_type in ipairs(section.generation_types) do
                string.format('mod_spawn_weights.tag IN ("%s")', table.concat(section_tags, '","')),
                 section.mods_data[gen_type.id] = {}
            }, ' AND ')
 
            local results = m_cargo.query(
                 -- Query mods
                 {
                 local where = {
                    'mods',
                     string.format('mods.domain = %s', section.domain or item_data.domain),
                    'mod_stats',
                     string.format('mods.generation_type = %s', gen_type.id),
                    'mod_spawn_weights',
                     'mods.stat_text IS NOT NULL',
                },
                     string.format('mod_spawn_weights.tag IN ("%s")', table.concat(section_tags, '","')),
                 {
                     'mods._pageID',
                    'mods._pageName',
                    'mods.name',
                    'mods.id',
                     'mods.required_level',
                    'mods.generation_type',
                    'mods.domain',
                    'mods.mod_group',
                    '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 = where,
                    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
                    ]],
                 }
                 }
            )
                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 modifiers
                -- Group results
            if #results > 0 then
                if #results > 0 then
                for _, row in ipairs(results) do
                    for _, row in ipairs(results) do
                    row['mods.mod_groups'] = m_util.cast.table(row['mods.mod_groups'])
                        row['mods.mod_groups'] = m_util.cast.table(row['mods.mod_groups'])
                    row['mods.tags'] = m_util.cast.table(row['mods.tags'])
                        row['mods.tags'] = m_util.cast.table(row['mods.tags'])
                    if #row['mods.mod_groups'] > 0 then
                        if #row['mods.mod_groups'] > 0 then
                        for _, group in ipairs(row['mods.mod_groups']) do
                            for _, group in ipairs(row['mods.mod_groups']) do
                            item_mods2[section.heading][group] = item_mods2[section.heading][group] or {}
                                section.mods_data[gen_type.id][group] = section.mods_data[gen_type.id][group] or {}
                            item_mods2[section.heading][group][row['mods.mod_type']] = item_mods2[section.heading][group][row['mods.mod_type']] 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(item_mods2[section.heading][group][row['mods.mod_type']], row)
                                table.insert(section.mods_data[gen_type.id][group][row['mods.mod_type']], row)
                            end
                         end
                         end
                     end
                     end
Line 631: Line 353:


             if tpl_args.debug then
             if tpl_args.debug then
                 mw.logObject(item_mods2)
                 mw.logObject(section.mods_data)
            end
 
 
 
 
 
 
            results = m_cargo.map_results_to_id{
                results=results,
                field='mods._pageName',
                keep_id_field=true,
                append_id_field=true,
            }
 
            if #results > 0 then
                -- Loop through all found modifiers:
                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]['mod_spawn_weights.tag']
                        local mod_tag_weight = tonumber(
                            results[id][j]['mod_spawn_weights.value']
                        )
 
                        -- 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 == section_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (results[id][j] == nil)
                            tag_match_add  =  (mod_tag == section_tags[y]) and ((mod_tag_weight or -1) > 0)
                        until tag_match_stop or y == #section_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[section.heading]
                            item_mods[section.heading][a+1] = results[id][j]
 
                            -- Save other interesting fields:
                            item_mods[section.heading][a+1]['mods.scope'] = mod_scope
                            item_mods[section.heading][a+1]['spawn_weight.idx_match'] = j
                            item_mods[section.heading][a+1]['mods.add'] = tag_match_add
                            item_mods[section.heading][a+1]['mods.stop'] = tag_match_stop
 
                            -- Count the mod groups:
                            local group = item_mods[section.heading][a+1]['mods.mod_group'] or 'nil_group'
                            for _, heading in ipairs({section.heading, 'all'}) do
                                if mod_group_counter[heading][adj][group] == nil then
                                    mod_group_counter[heading][adj][group] = {}
                                end
                                local tp = results[id][j]['mods.mod_type']
                                local bef = mod_group_counter[heading][adj][group][tp] or 0
                                mod_group_counter[heading][adj][group][tp] = 1 + bef
                            end
                        end
                    until tag_match_stop
                end
             end
             end
         end
         end
     end
     end


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



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