Module:Modifier compatibility/sandbox: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 535: Line 535:
     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 = {}
     local mods_data = {}
     local mod_group_counter = {}
    mod_group_counter['all'] = {}
    local table_index_base = -1
     for _, section in ipairs(c.item_mods.sections) do
     for _, section in ipairs(c.item_mods.sections) do
         -- Show the section? Default: Show
         -- Show the section? Default: Show
Line 547: Line 544:
         end
         end
         if show then
         if show then
             item_mods[section.heading] = {}
             mods_data[section.heading] = {}
            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
Line 619: Line 604:
                     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 {}
                             mods_data[section.heading][group] = mods_data[section.heading][group] or {}
                             item_mods2[section.heading][group][row['mods.mod_type']] = item_mods2[section.heading][group][row['mods.mod_type']] or {}
                             mods_data[section.heading][group][row['mods.mod_type']] = mods_data[section.heading][group][row['mods.mod_type']] or {}
                             table.insert(item_mods2[section.heading][group][row['mods.mod_type']], row)
                             table.insert(mods_data[section.heading][group][row['mods.mod_type']], row)
                         end
                         end
                     end
                     end
Line 628: Line 613:


             if tpl_args.debug then
             if tpl_args.debug then
                 mw.logObject(item_mods2)
                 mw.logObject(mods_data)
             end
             end
         end
         end
Line 638: Line 623:
     local html = mw.html.create()
     local html = mw.html.create()
     for _, section in ipairs(c.item_mods.sections) do
     for _, section in ipairs(c.item_mods.sections) do
         if m_util.table.length(item_mods2[section.heading]) > 0 then
         if m_util.table.length(mods_data[section.heading]) > 0 then
             local list_container = html:tag('div')
             local list_container = html:tag('div')
                 :tag('h4')
                 :tag('h4')
                     :wikitext(section.heading)
                     :wikitext(section.heading)
                     :done()
                     :done()
             for gkey, gval in pairs(item_mods2[section.heading]) do
             for gkey, gval in pairs(mods_data[section.heading]) do
                 local group_list = list_container:tag('dl')
                 local group_list = list_container:tag('dl')
                     :addClass('accordion')
                     :addClass('accordion')

Revision as of 22:31, 11 November 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')

-- Lazy loading
local f_item_link -- require('Module:Item link').item_link

-- 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 = {}

-- Lazy loading for Module:Item link
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.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

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

-- ----------------------------------------------------------------------------
-- Additional configuration
-- ----------------------------------------------------------------------------

local c = {}

c.item_mods = {}

c.item_mods.sections = {
    {
        heading = i18n.item_mods.prefix,
        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.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,
    },
    {
        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_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,
    },
    {
        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,
    },
    {
        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_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.crusader}
        end,
        heading = i18n.item_mods.crusader_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,
    },
    {
        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,
    },
    {
        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_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.basilisk}
        end,
        heading = i18n.item_mods.basilisk_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,
    },
    {
        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.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,
    },
    {
        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.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,
    },
    {
        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.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,
    },
    {
        show = function (item_data)
            return item_data.can_be_corrupted
        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,
        is_implicit = true,
    },
}

-- ----------------------------------------------------------------------------
-- 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
        return item_data.error
    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

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

    -- 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
    local mods_data = {}
    for _, section in ipairs(c.item_mods.sections) do
        -- 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
            mods_data[section.heading] = {}
            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
            -- Query mods and map the results to the pagename
            local where = table.concat({
                section.where(item_data),
                'mods.stat_text IS NOT NULL',
                string.format('mod_spawn_weights.tag IN ("%s")', table.concat(section_tags, '","')),
            }, ' AND ')
            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_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
                    ]],
                }
            )

            -- Group modifiers
            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
                            mods_data[section.heading][group] = mods_data[section.heading][group] or {}
                            mods_data[section.heading][group][row['mods.mod_type']] = mods_data[section.heading][group][row['mods.mod_type']] or {}
                            table.insert(mods_data[section.heading][group][row['mods.mod_type']], row)
                        end
                    end
                end
            end

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

    --
    -- Display the item mods
    --
    local html = mw.html.create()
    for _, section in ipairs(c.item_mods.sections) do
        if m_util.table.length(mods_data[section.heading]) > 0 then
            local list_container = html:tag('div')
                :tag('h4')
                    :wikitext(section.heading)
                    :done()
            for gkey, gval in pairs(mods_data[section.heading]) do
                local group_list = list_container:tag('dl')
                    :addClass('accordion')
                local group_heading = group_list:tag('dt')
                    :addClass('accordion-heading')
                local group_body = group_list:tag('dd')
                    :addClass('accordion-body')
                group_heading
                    :tag('div')
                        :addClass('mod-compat__group-label')
                        :wikitext( string.format('%s %s', i18n.item_mods.group, gkey) )
                        :done()
                local summary = group_heading:tag('ul')
                    :addClass('mod-compat__group_summary')
                local mod_table = group_body:tag('table')
                    :addClass('wikitable modifier-table')
                mod_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()
                for tkey, tval in pairs(gval) do
                    local text = tval[1]['mods.stat_text_raw']
                    if m_util.table.length(tval) > 1 then
                        text = h.genericize_stat_text(text)
                    end
                    summary
                        :tag('li')
                            :wikitext( m_util.html.poe_color('mod', text) )
                            :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)
                                    :done()
                        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
    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