Module:Item acquisition: Difference between revisions

From Path of Exile 2 Wiki
Jump to navigation Jump to search
>OmegaK2
(Fixed sets not appearing in ingredients, forced order)
No edit summary
 
(55 intermediate revisions by 8 users not shown)
Line 1: Line 1:
-- ----------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Imports
--  
-- ----------------------------------------------------------------------------
--                           Module:Item acquisition
--
-- This module implements Template:Item acquisition.
-------------------------------------------------------------------------------


local getArgs = require('Module:Arguments').getArgs
require('Module:No globals')
local m_util = require('Module:Util')
local m_util = require('Module:Util')
local f_item_link = require('Module:Item link').item_link
local m_cargo = require('Module:Cargo')
local m_item_util = require('Module:Item util')
local m_quest_reward = require('Module:Quest reward')._shared
 
local m_game = mw.loadData('Module:Game')


-- ----------------------------------------------------------------------------
-- Should we use the sandbox version of our submodules?
-- Strings
local use_sandbox = m_util.misc.maybe_sandbox('Item acquisition')
-- ----------------------------------------------------------------------------
 
-- This section contains strings used by this module.
-- Lazy loading
-- Add new strings here instead of in-code directly, this will help other
local f_item_link -- require('Module:Item link').item_link
-- people to correct spelling mistakes easier and help with translation to
-- other PoE wikis.
--
-- TODO: Maybe move this out to a separate sub-page module
local i18n = {
    acquisition = {
        header = 'Item acquisition',
        area_header = 'Area restrictions',
        area_legacy_header = 'Legacy area restrictions',
        area = 'This item can be acquired in the following areas:',
       
        upgraded_from_header = 'Upgrade paths',
        upgraded_from = 'This item can be acquired through the following upgrade paths or vendor recipes:',
       
        ingredient_header = 'Usage in upgrade paths',
        ingredient = 'This item is used by upgrade paths or vendor recipes to create the following items:',
    },
}


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


local c = {}
local i18n = cfg.i18n


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
Line 42: Line 31:


local h = {}
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.head(str, level)
function h.head(str, level)
     local head = mw.html.create('h' .. (level or 3))
     local head = mw.html.create('h' .. (level or 3))
Line 48: Line 46:
end
end


function h.fetch_upgraded_from_sets(where_set, where_group)
function h.fetch_recipes(where_set, where_group)
     if where_set == "" or where_group == "" then
     if where_set == "" or where_group == "" then
         return {}, {}
         return {}, {}
Line 54: Line 52:
      
      
     local sets = {}
     local sets = {}
     local results = m_util.cargo.query(
     local results = m_cargo.query(
         {'upgraded_from_sets'},
         {'acquisition_recipes'},
         {
         {
             'upgraded_from_sets._pageName',
             'acquisition_recipes._pageName',
             'upgraded_from_sets.set_id',
             'acquisition_recipes.recipe_id',
             'upgraded_from_sets.text',
             'acquisition_recipes.result_amount',
            'acquisition_recipes.description',
            'acquisition_recipes.automatic',
         },
         },
         {
         {
Line 68: Line 68:
     for _, row in ipairs(results) do
     for _, row in ipairs(results) do
         row.groups = {}
         row.groups = {}
         sets[row['upgraded_from_sets._pageName'] .. tonumber(row['upgraded_from_sets.set_id'])] = row
         sets[row['acquisition_recipes._pageName'] .. tonumber(row['acquisition_recipes.recipe_id'])] = row
     end
     end
      
      
     results = m_util.cargo.query(
     results = m_cargo.query(
         {'upgraded_from_groups'},
         {'acquisition_recipe_parts'},
         {
         {
             'upgraded_from_groups._pageName',
             'acquisition_recipe_parts._pageName',
             'upgraded_from_groups.set_id',
             'acquisition_recipe_parts.recipe_id',
             'upgraded_from_groups.group_id',
             'acquisition_recipe_parts.part_id',
             'upgraded_from_groups.notes',
             'acquisition_recipe_parts.notes',
             'upgraded_from_groups.amount',
             'acquisition_recipe_parts.amount',
             'upgraded_from_groups.item_name',
             'acquisition_recipe_parts.item_name',
             'upgraded_from_groups.item_page',
             'acquisition_recipe_parts.item_page',
         },
         },
         {
         {
Line 88: Line 88:
      
      
     for _, row in ipairs(results) do
     for _, row in ipairs(results) do
         sets[row['upgraded_from_groups._pageName'] .. tonumber(row['upgraded_from_groups.set_id'])].groups[tonumber(row['upgraded_from_groups.group_id'])] = row
         sets[row['acquisition_recipe_parts._pageName'] .. tonumber(row['acquisition_recipe_parts.recipe_id'])].groups[tonumber(row['acquisition_recipe_parts.part_id'])] = row
     end
     end
      
      
Line 96: Line 96:
     end
     end
      
      
     table.sort(sets_sort)
     table.sort(sets_sort, function (a, b)
        return tonumber(sets[a]['acquisition_recipes.recipe_id']) < tonumber(sets[b]['acquisition_recipes.recipe_id'])
    end)
      
      
     return sets, sets_sort
     return sets, sets_sort
end
end


-- ----------------------------------------------------------------------------
function h.recipe_table(tpl_args, item, sets, set_order, data_type, out, item_pages)
-- Templates
   
-- ----------------------------------------------------------------------------
    -- Count the number of item pages:
    local item_pages_count = 0
    for _,__ in pairs(item_pages) do
        item_pages_count = item_pages_count + 1
    end
   
    -- Avoid creating headers only when no data is given
    if #set_order <= 0 then
        return
    end
   
    -- Prevent showing either group or set notes when no rows have any to save a bit of vertical space
    local set_notes = false
    local group_notes = false
    for _, set_key in ipairs(set_order) do
        if set_notes and group_notes then
            break
        end
        local set = sets[set_key]
        if set['acquisition_recipes.description'] ~= nil then
            set_notes = true
        end
       
        if #set.groups > 0 then
            for i, group in ipairs(set.groups) do
                if group['acquisition_recipe_parts.notes'] ~= nil then
                    group_notes = true
                    break
                end
            end
        end
    end
   
    local tbl = mw.html.create('table')
   
    -- sorting will mess up the table because of the rowspawns
    local table_class = 'wikitable responsive-table'
    --[[if item_pages_count > cfg.COLLAPSE_TABLE then
        table_class = 'wikitable mw-collapsible mw-collapsed'
    end--]]
    tbl:attr('class', table_class)
   
    --
    -- Header
    --  
    local tr = tbl:tag('tr')
    if data_type == 'ingredient' then
        tr
            :tag('th')
                :wikitext(i18n.recipe_table.result)
    end
   
    tr
        :tag('th')
            :wikitext(i18n.recipe_table.part_amount)
            :done()
        :tag('th')
            :wikitext(i18n.recipe_table.part)
            :done()
    if group_notes then
        tr
            :tag('th')
                :wikitext(i18n.recipe_table.part_notes)
                :done()
    end
    if set_notes then
        tr
            :tag('th')
                :wikitext(i18n.recipe_table.description)
                :done()
    end
    tr
        :tag('th')
            :wikitext(i18n.recipe_table.meta)
            :done()
    --
    -- Rows
    --
   
    for _, set_key in ipairs(set_order) do
        local set = sets[set_key]
       
        if #set.groups > 0 then
            tr = tbl:tag('tr')
            tr:attr('class', 'upgraded-from-set')
           
            if data_type == 'ingredient' then
                local str
                if tpl_args.no_link == nil and item_pages_count < cfg.MAX_ITEMS then
                    local item_data = item_pages[set['acquisition_recipes._pageName']][1]
                    str = h.item_link{page=set['acquisition_recipes._pageName'], name=item_data['items.name'], inventory_icon=item_data['items.inventory_icon'] or '', html=item_data['items.html'] or '', skip_query=true}
                else
                    str = string.format('[[%s]]', set['acquisition_recipes._pageName'])
                end
               
                tr
                    :tag('td')
                        :attr('rowspan', #set.groups)
                        :wikitext(str)
                        :done()
            end
           
            local tr2
            for i, group in ipairs(set.groups) do
                if i <= 1 then
                    tr2 = tr
                else
                    tr2 = tbl:tag('tr')
                end
                local str
                if tpl_args.no_link == nil and item_pages_count < cfg.MAX_ITEMS then
                    local item_data = item_pages[group['acquisition_recipe_parts.item_page']][1]
                    str = h.item_link{
                        page=group['acquisition_recipe_parts.item_page'],
                        name=item_data['items.name'],
                        inventory_icon=item_data['items.inventory_icon'] or '',
                        html=item_data['items.html'] or '',
                        skip_query=true
                    }
                else
                    str = string.format('[[%s]]', group['acquisition_recipe_parts.item_page'])
                end
               
                tr2
                    :tag('td')
                        :attr('data-sort-type', 'number')
                        :wikitext(group['acquisition_recipe_parts.amount'])
                        :done()
                    :tag('td')
                        :wikitext(str)
                        :done()
               
                if group_notes then
                    if group['acquisition_recipe_parts.notes'] then
                        tr2
                            :tag('td')
                                :wikitext(group['acquisition_recipe_parts.notes'])
                    else
                        tr2
                            :node(
                                m_util.html.table_cell('na')
                            )
                    end
                end
            end
           
            if set_notes then
                if set['acquisition_recipes.description'] then
                    tr
                        :tag('td')
                            :attr('rowspan', #set.groups)
                            :wikitext(set['acquisition_recipes.description'])
                else
                    tr
                        :node(
                            m_util.html.table_cell('na')
                                :attr('rowspan', #set.groups)
                        )
                end
            end
           
            local t
            if set['acquisition_recipes.automatic'] == '1' then
                t = i18n.recipe_table.automatic
            else
                t = i18n.recipe_table.manual
            end
           
            tr
                :tag('td')
                    :attr('rowspan', #set.groups)
                    :wikitext(t)
        end
    end
   
   
    --
    -- print
    --
   
    if data_type == 'ingredient' then
        out[#out+1] = h.head(i18n.acquisition.recipe_usage_heading)
        out[#out+1] = string.format(i18n.acquisition.recipe_usage_text, item.name)
    else
        out[#out+1] = h.head(i18n.acquisition.recipes_heading)
        out[#out+1] = string.format(i18n.acquisition.recipes_text, item.name)
    end
    out[#out+1] = '<br>'
    out[#out+1] = tostring(tbl)
end


local p = {}
function h.reward_table(data, rtbl)
    if #data == 0 then
        return
    end
   
    local tbl = m_quest_reward.reward_tbl_head()
           
    for _, row in ipairs(data) do
        local classes
        if row[rtbl .. '.classes'] then
            classes = {}
            for _, class in ipairs(m_util.string.split(row[rtbl .. '.classes'], ',')) do
                classes[class] = true
            end
        end
       
        local tr = tbl:tag('tr')
        m_quest_reward.reward_tbl_row_head(tr, rtbl, row)


--
        if classes then
-- Template: Item acquisition
            for _, class in ipairs(m_game.constants.characters_order) do
--
                if classes[m_game.constants.characters[class].name] then
                    tr
                        :node(
                            m_util.html.table_cell('yes')
                        )
                else
                    tr
                        :node(
                            m_util.html.table_cell('no')
                        )
                end
            end
        else
            tr
                :node(
                    m_util.html.table_cell('yes')
                        :attr('colspan', 7)
                )
        end
    end
   
    return h.head(i18n.quest_reward[rtbl .. '_header']) .. i18n.quest_reward[rtbl .. '_intro'] .. tostring(tbl)
end


-- Used to duplicate the information from the infobox in a more readable manner on the page.
-- ----------------------------------------------------------------------------
-- Main functions
-- ----------------------------------------------------------------------------


function p.item_acquisition (frame)
local function _main(tpl_args)
     -- Get args
     --[[
     local tpl_args = getArgs(frame, {
     Duplicates the information from the infobox in a more readable
        parentFirst = true
     manner on the page.
     })
    frame = m_util.misc.get_frame(frame)
      
      
     tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
     Example
    -------
    = p.item_acquisition{page='The Rite of Elements'}
    ]]
 
    local out = {}
    out[#out+1] = tpl_args.acquisition_insert
      
      
     local out = {}
    -- fetch general item drop information that is used in multiple places in a
     local results
    -- single query to reduce performance hit
     local query
     local tables = {'items'}
     local fields = {
        'items._pageID=_pageID',
        'items._pageName=_pageName',
        'items.name=name',
        'items.class_id=class_id',
        'items.rarity_id=rarity_id',
        'items.base_item_page=base_item_page',
        'items.drop_text=drop_text',
        'items.acquisition_tags=acquisition_tags',
        'items.drop_areas=drop_areas',
        'items.drop_monsters=drop_monsters',
        'items.drop_enabled=drop_enabled',
        'items.is_drop_restricted=is_drop_restricted',
    }
    local query = {
        limit = 1,
    }
    if tpl_args.page then
        -- Join with _pageData in order to check for page redirect
        tables[#tables+1] = '_pageData'
        query.join = 'items._pageName = _pageData._pageNameOrRedirect'
        query.where = string.format(
            '_pageData._pageName="%s"',
            tpl_args.page
        )
    else
        query.where = string.format(
            'items._pageName="%s"',
            tostring(mw.title.getCurrentTitle())
        )
    end
     local item_data = m_cargo.query(tables, fields, query)
    if #item_data > 0 then
        item_data = item_data[1]
    end
    if item_data == nil then
        error("Item search unexpectedly returned no results")
    elseif item_data.name == nil then
        error("Item search result unexpectedly has no name")
    end
      
      
    -- ------------------------------------------------------------------------
    -- General drop restrictions
    -- ------------------------------------------------------------------------
    local drop_enabled = m_util.cast.boolean(item_data.drop_enabled)
    local drop_restricted = m_util.cast.boolean(item_data.is_drop_restricted)
    local is_unique = item_data.rarity_id == 'unique'
    if not drop_enabled then
        out[#out+1] = string.format(i18n.acquisition.drop_disabled, item_data.name)
    elseif drop_restricted then
        -- Acquisition unkown
        if not item_data.drop_areas and not item_data.drop_monsters and not item_data.drop_text then
            -- Divination cards
            if item_data.class_id == 'DivinationCard' then
                out[#out+1] = mw.getCurrentFrame():expandTemplate{title = i18n.templates.divination_card_acquisition_unknown}
                out[#out+1] = '\n\n'
            end
        end
        out[#out+1] = string.format(i18n.acquisition.drop_restricted, item_data.name)
        if is_unique then
            out[#out+1] = ' '
            out[#out+1] = i18n.acquisition.cannot_be_chanced
        end
    else
        if not item_data.drop_areas then
            out[#out+1] = string.format(i18n.acquisition.drop_anywhere, item_data.name)
        end
        if is_unique then
            local base_item_data = m_cargo.query(
                {'items'},
                {
                    'items.is_corrupted=is_corrupted',
                    'items.drop_enabled=drop_enabled',
                },
                {
                    where = string.format('items._pageName="%s"', item_data.base_item_page),
                    groupBy = 'items._pageID',
                }
            )
            if #base_item_data > 0 then
                base_item_data = base_item_data[1]
                if m_util.cast.boolean(base_item_data.drop_enabled) and not m_util.cast.boolean(base_item_data.is_corrupted) then
                    out[#out+1] = ' '
                    out[#out+1] = i18n.acquisition.can_be_chanced
                end
            end
        end
    end
      
      
     out[#out+1] = tpl_args.acquisition_insert
     -- ------------------------------------------------------------------------
    -- League-specific
    -- ------------------------------------------------------------------------
    local acquisition_tags = {}
    if item_data.acquisition_tags then
        acquisition_tags = m_util.cast.table(item_data.acquisition_tags)
    end
    if m_util.table.contains(acquisition_tags, 'league-specific') and drop_enabled and is_unique then
        out[#out+1] = '\n\n'
        out[#out+1] = string.format(i18n.acquisition.league_specific_unique, item_data.name)
    end
      
      
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
     -- Drop restrictions by text
     -- Drop restrictions by text
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
     results = m_util.cargo.query(
     if item_data.drop_text and drop_enabled then
        {'items'},
         out[#out+1] = '\n\n'
        {
        out[#out+1] = item_data.drop_text
            'items.drop_text',
        },
        {
            where=string.format('items._pageName="%s"', tpl_args.page),
            limit=1,
        }
    )
       
    if #results > 0 then
         results = results[1]
        if results['items.drop_text'] then
            out[#out+1] = results['items.drop_text']
        end
     end
     end
      
      
Line 153: Line 474:
     -- Drop restrictions by area
     -- Drop restrictions by area
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
    local drop_areas = {}
    if item_data.drop_areas then
        drop_areas = m_util.cast.table(item_data.drop_areas)
    end
    if #drop_areas > 0 then
        local results = m_cargo.query(
            {'areas'},
            {
                'areas._pageName=_pageName',
                'areas.id=id',
                'areas.name=name',
                'areas.main_page=main_page',
            },
            {
                where = string.format('areas.id IN ("%s") AND NOT areas.is_legacy_map_area', table.concat(drop_areas, '","')),
                orderBy = 'areas.name ASC',
                groupBy = 'areas._pageID',
            }
        )
        if #results > 0 then
            local ul = mw.html.create('ul')
            for _, row in ipairs(results) do
                ul:tag('li')
                    :wikitext(m_util.html.wikilink(row.main_page or row._pageName, row.name))
            end
            out[#out+1] = h.head(i18n.acquisition.area_header)
            out[#out+1] = i18n.acquisition.area
            out[#out+1] = tostring(ul)
        end
    end
      
      
     local query_sets = {
     -- ------------------------------------------------------------------------
        {
    -- Drop restrictions by monster
            header=i18n.acquisition.area_header,
    -- ------------------------------------------------------------------------
            condition='areas.id NOT LIKE "MapAtlas%" AND areas.id NOT LIKE "Map2%"',
    local monster_metadata_ids = {}
            order='areas.name ASC',
    if item_data.drop_monsters then
         },
         monster_metadata_ids = m_util.cast.table(item_data.drop_monsters)
        {
    end
            header=i18n.acquisition.area_legacy_header,
              
            condition='areas.id LIKE "MapAtlas%" OR areas.id LIKE "Map2%"',
     if #monster_metadata_ids > 0 then
            order=[[
         local results = m_cargo.query(
                CASE
             {'monsters', 'main_pages'},
                    WHEN areas.id LIKE "MapAtlas%" THEN 1
                    WHEN areas.id LIKE "Map2%" THEN 2
                    ELSE 3
                END ASC,
                areas.name ASC
             ]],
        },
     }
   
    for _, query_set in ipairs(query_sets) do
         results = m_util.cargo.query(
             {'items', 'items__drop_areas', 'areas'},
             {
             {
                 'areas._pageName',
                 'monsters._pageName',
                 'areas.id',
                 'monsters.metadata_id',
                 'areas.name',
                 'monsters.name',
                 'areas.main_page',
                 -- 'monsters.main_page',
                'main_pages._pageName',
             },
             },
             {
             {
                 where=string.format('items._pageName="%s" AND (%s)', tpl_args.page, query_set.condition),
                join='monsters.metadata_id=main_pages.id',
                join='items._ID=items__drop_areas._rowID, items__drop_areas._value=areas.id',
                 where=string.format('monsters.metadata_id IN ("%s")', table.concat(monster_metadata_ids, '","')),
                 orderBy=query_set.order,
                 orderBy='monsters.name',
                 groupBy='areas.id',
                 groupBy='monsters.metadata_id',
             }
             }
         )
         )
Line 194: Line 534:
             for _, row in ipairs(results) do
             for _, row in ipairs(results) do
                 ul:tag('li')
                 ul:tag('li')
                     :wikitext(string.format('[[%s|%s]]', row['areas.main_page'] or row['areas._pageName'], row['areas.name']))
                     :wikitext(string.format(
             end          
                        '[[%s|%s]]',  
             out[#out+1] = h.head(query_set.header)
                          row['monsters.main_page']  
             out[#out+1] = i18n.acquisition.area
                        or row['main_pages._pageName']
                        or row['monsters._pageName'],  
                        row['monsters.name']
                    ))
             end
             out[#out+1] = h.head(i18n.acquisition.monster_header)
             out[#out+1] = i18n.acquisition.monster
             out[#out+1]= '<br>'
             out[#out+1]= '<br>'
             out[#out+1] = tostring(ul)
             out[#out+1] = tostring(ul)
Line 204: Line 550:
      
      
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
     -- Vendor recipes/upgrades handling
     -- Recipes section
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
      
      
     --
     --
     -- Query set data
     -- Query recipe data
     --
     --
     local obtained_sets, obtained_sets_order = h.fetch_upgraded_from_sets(
     local obtained_sets, obtained_sets_order = h.fetch_recipes(
         string.format('upgraded_from_sets._pageName="%s"', tpl_args.page),
         string.format('acquisition_recipes._pageID="%s"', item_data._pageID),
         string.format('upgraded_from_groups._pageName="%s"', tpl_args.page)
         string.format('acquisition_recipe_parts._pageID="%s"', item_data._pageID)
     )
     )
      
      
     results = m_util.cargo.query(
     -- Find recipes that item is used in
        {'upgraded_from_groups'},
    local tables = {'acquisition_recipe_parts'}
        {
    local fields = {
            'upgraded_from_groups._pageID',
        '_pageID',
            'upgraded_from_groups.set_id',
        'recipe_id',
        },
    }
        {
    local query = {
            where=string.format('upgraded_from_groups.item_page="%s"', tpl_args.page),
        where = string.format('item_page="%s"', item_data._pageName),
            -- only need one result set for the where clause
        groupBy = '_pageID, recipe_id',
            groupBy='upgraded_from_groups._pageID, upgraded_from_groups.set_id',
    }
        }
 
     )
     -- Namespace condition
     local where = {sets={}, groups={}}
    -- This is mainly to prevent items from user pages or other testing pages
    for key, data in pairs(where) do
    -- from being returned in the query results.
         for _, row in ipairs(results) do
     if tpl_args.namespaces ~= 'any' then
             data[#data+1] = string.format('upgraded_from_%s.set_id="%s" AND upgraded_from_%s._pageID="%s"', key, row['upgraded_from_groups.set_id'], key, row['upgraded_from_groups._pageID'])
        local namespaces = m_util.cast.table(tpl_args.namespaces, {callback=m_util.cast.number})
         if #namespaces > 0 then
             namespaces = table.concat(namespaces, ',')
        else
            namespaces = m_item_util.get_item_namespaces{format = 'list'}
         end
         end
         where[key] = table.concat(data, ' OR ')
         query.where = string.format('%s AND _pageNamespace IN (%s)', query.where, namespaces)
     end
     end
     local ingredient_sets, ingredient_sets_order = h.fetch_upgraded_from_sets(where.sets, where.groups)
 
    local results = m_cargo.query(tables, fields, query)
    local recipes = {}
    local parts = {}
    for _, row in ipairs(results) do
        recipes[#recipes+1] = string.format('acquisition_recipes._pageID="%s" AND acquisition_recipes.recipe_id="%s"', row._pageID, row.recipe_id)
        parts[#parts+1] = string.format('acquisition_recipe_parts._pageID="%s" AND acquisition_recipe_parts.recipe_id="%s"', row._pageID, row.recipe_id)
    end
    parts = table.concat(parts, ' OR ')
    recipes = table.concat(recipes, ' OR ')
 
     local ingredient_sets, ingredient_sets_order = h.fetch_recipes(recipes, parts)
     --
     --
     -- Query bulk item info for item linking
     -- Query bulk item info for item linking
Line 243: Line 604:
             local set = set_data[1][set_key]
             local set = set_data[1][set_key]
             for _, group in ipairs(set.groups) do
             for _, group in ipairs(set.groups) do
                 item_pages.assoc[group['upgraded_from_groups.item_page']] = true
                 item_pages.assoc[group['acquisition_recipe_parts.item_page']] = true
             end
             end
             item_pages.assoc[set['upgraded_from_sets._pageName']] = true
             item_pages.assoc[set['acquisition_recipes._pageName']] = true
         end
         end
     end
     end
Line 253: Line 614:
     end
     end
      
      
     item_pages = m_util.cargo.map_results_to_id{
     if #item_pages < cfg.MAX_ITEMS then
        results=m_util.cargo.array_query{
        item_pages = m_cargo.map_results_to_id{
            tables={'items'},
            results=m_cargo.array_query{
            fields={'items.name', 'items.inventory_icon', 'items.html'},
                tables={'items'},
            id_array = item_pages,
                fields={'items.name', 'items.inventory_icon', 'items.html'},
            id_field = 'items._pageName',
                id_array = item_pages,
        },
                id_field = 'items._pageName',
        field='items._pageName',
                query = {
        keep_id_field=true,
                    limit=5000
     }
                },
            },
            field='items._pageName',
            keep_id_field=true,
        }
    end
      
     --  
     --  
     -- Output for being obtained via vendor recipe
     -- Output for being obtained via vendor recipe
     --
     --
      
      
     if #obtained_sets_order > 0 then
     h.recipe_table(tpl_args, item_data, obtained_sets, obtained_sets_order, 'obtained', out, item_pages)
        local ul = mw.html.create('ul')
        for _, set_key in ipairs(obtained_sets_order) do
            local set = obtained_sets[set_key]
            local li = ul:tag('li')
            if set['upgraded_from_sets.text'] then
                li:wikitext(set['upgraded_from_sets.text'] .. '<br>')
            end
           
            local str = {}
            for _, group in ipairs(set.groups) do
                local item_data = item_pages[group['upgraded_from_groups.item_page']][1]
                str[#str+1] = string.format('%sx %s', group['upgraded_from_groups.amount'], f_item_link{page=item_data['upgraded_from_groups.item_page'], name=item_data['items.name'], inventory_icon=item_data['items.inventory_icon'] or '', html=item_data['items.html'] or '', skip_query=true})
                if group['upgraded_from_groups.notes'] then
                    str[#str] = string.format('%s (%s)', str[#str], group['upgraded_from_groups.notes'])
                end
            end
            li:wikitext(table.concat(str, ', '))
        end
   
        out[#out+1] = h.head(i18n.acquisition.upgraded_from_header)
        out[#out+1] = i18n.acquisition.upgraded_from
        out[#out+1]= '<br>'
        out[#out+1] = tostring(ul)
    end
      
      
     --  
     --  
Line 297: Line 640:
     --  
     --  
      
      
     if #ingredient_sets_order > 0 then
     h.recipe_table(tpl_args, item_data, ingredient_sets, ingredient_sets_order, 'ingredient', out, item_pages)
        out[#out+1] = h.head(i18n.acquisition.ingredient_header)
   
        out[#out+1] = i18n.acquisition.ingredient
    out[#out+1] = tpl_args.ingredient_append
        out[#out+1]= '<br>'
   
    -- ------------------------------------------------------------------------
    -- Obtained via quest or vendor reward
    -- ------------------------------------------------------------------------
      
      
        local ul = mw.html.create('ul')
    local quest_rewards = m_cargo.query(
         for _, set_key in ipairs(ingredient_sets_order) do
         {'quest_rewards'},
            local set = ingredient_sets[set_key]
        {
            local li = ul:tag('li')
             'quest_rewards.quest',
           
            'quest_rewards.act',
            local str = {}
            'quest_rewards.classes',
            local item_data
            'quest_rewards.sockets',
             for _, group in ipairs(set.groups) do
            'quest_rewards.item_level',
                local item_data = item_pages[group['upgraded_from_groups.item_page']][1]
            'quest_rewards.rarity',
                str[#str+1] = string.format('%sx %s', group['upgraded_from_groups.amount'], f_item_link{page=group['upgraded_from_groups.item_page'], name=item_data['items.name'], inventory_icon=item_data['items.inventory_icon'] or '', html=item_data['items.html'] or '', skip_query=true})
        },
                if group['upgraded_from_groups.notes'] then
        {
                    str[#str] = string.format('%s [%s]', str[#str], group['upgraded_from_groups.notes'])
            where=string.format('quest_rewards._pageName="%s"', item_data._pageName),
                end
            orderBy='quest_rewards.act ASC, quest_rewards.quest_id ASC',
            end
        }
            item_data = item_pages[set['upgraded_from_sets._pageName']][1]
    )
            li:wikitext(string.format('%s (%s)',  
    out[#out+1] = h.reward_table(quest_rewards, 'quest_rewards')
                f_item_link{page=set['upgraded_from_sets._pageName'], name=item_data['items.name'], inventory_icon=item_data['items.inventory_icon'] or '', html=item_data['items.html'] or '', skip_query=true},
                table.concat(str, ', ')
            ))
        end
       
        out[#out+1] = tostring(ul)
    end
      
      
     out[#out+1] = tpl_args.ingredient_append
    local vendor_rewards = m_cargo.query(
        {'vendor_rewards'},
        {
            'vendor_rewards.quest',
            'vendor_rewards.act',
            'vendor_rewards.classes',
            'vendor_rewards.npc',
        },
        {
            where=string.format('vendor_rewards._pageName="%s"', item_data._pageName),
            orderBy='vendor_rewards.act ASC, vendor_rewards.quest_id ASC',
        }
    )
     out[#out+1] = h.reward_table(vendor_rewards, 'vendor_rewards')
      
      
     -- ------------------------------------
     -- ------------------------------------
Line 333: Line 685:
      
      
     local head = mw.html.create('h2')
     local head = mw.html.create('h2')
     head:wikitext(i18n.acquisition.header .. '[[File:Questionmark.png|right|24px|link=Path_of_Exile_Wiki:How_to_edit_item_acquisition]]')
     head:wikitext(i18n.acquisition.header .. i18n.acquisition.link)
     return tostring(head) .. table.concat(out)
     return tostring(head) .. table.concat(out)
end
end


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- Return
-- Exported functions
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
local p = {}
--
-- Template: Item acquisition
--
p.main = m_util.misc.invoker_factory(_main, {
    wrappers = cfg.wrapper,
})
p.item_acquisition = p.main


return p
return p

Latest revision as of 18:19, 23 October 2024

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


This module implements {{item acquisition}}.

-------------------------------------------------------------------------------
-- 
--                           Module:Item acquisition
-- 
-- This module implements Template:Item acquisition.
-------------------------------------------------------------------------------

require('Module:No globals')
local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')
local m_item_util = require('Module:Item util')
local m_quest_reward = require('Module:Quest reward')._shared

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

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

-- 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:Item acquisition/config/sandbox') or mw.loadData('Module:Item acquisition/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.head(str, level)
    local head = mw.html.create('h' .. (level or 3))
    head:wikitext(str)
    return tostring(head)
end

function h.fetch_recipes(where_set, where_group)
    if where_set == "" or where_group == "" then
        return {}, {}
    end
    
    local sets = {}
    local results = m_cargo.query(
        {'acquisition_recipes'},
        {
            'acquisition_recipes._pageName',
            'acquisition_recipes.recipe_id',
            'acquisition_recipes.result_amount',
            'acquisition_recipes.description',
            'acquisition_recipes.automatic',
        },
        {
            where=where_set,
        }
    )
    
    for _, row in ipairs(results) do
        row.groups = {}
        sets[row['acquisition_recipes._pageName'] .. tonumber(row['acquisition_recipes.recipe_id'])] = row
    end
    
    results = m_cargo.query(
        {'acquisition_recipe_parts'},
        {
            'acquisition_recipe_parts._pageName',
            'acquisition_recipe_parts.recipe_id',
            'acquisition_recipe_parts.part_id',
            'acquisition_recipe_parts.notes',
            'acquisition_recipe_parts.amount',
            'acquisition_recipe_parts.item_name',
            'acquisition_recipe_parts.item_page',
        },
        {
            where=where_group,
        }
    )
    
    for _, row in ipairs(results) do
        sets[row['acquisition_recipe_parts._pageName'] .. tonumber(row['acquisition_recipe_parts.recipe_id'])].groups[tonumber(row['acquisition_recipe_parts.part_id'])] = row
    end
    
    local sets_sort = {}
    for key, _ in pairs(sets) do
        sets_sort[#sets_sort+1] = key
    end
    
    table.sort(sets_sort, function (a, b)
        return tonumber(sets[a]['acquisition_recipes.recipe_id']) < tonumber(sets[b]['acquisition_recipes.recipe_id'])
    end)
    
    return sets, sets_sort
end

function h.recipe_table(tpl_args, item, sets, set_order, data_type, out, item_pages)
    
    -- Count the number of item pages:
    local item_pages_count = 0
    for _,__ in pairs(item_pages) do 
        item_pages_count = item_pages_count + 1
    end
    
    -- Avoid creating headers only when no data is given
    if #set_order <= 0 then
        return
    end
    
    -- Prevent showing either group or set notes when no rows have any to save a bit of vertical space
    local set_notes = false
    local group_notes = false
    for _, set_key in ipairs(set_order) do
        if set_notes and group_notes then
            break
        end
        local set = sets[set_key]
        if set['acquisition_recipes.description'] ~= nil then
            set_notes = true
        end
        
        if #set.groups > 0 then
            for i, group in ipairs(set.groups) do
                if group['acquisition_recipe_parts.notes'] ~= nil then
                    group_notes = true
                    break
                end
            end
        end
    end
    
    local tbl = mw.html.create('table')
    
    -- sorting will mess up the table because of the rowspawns
    local table_class = 'wikitable responsive-table'
    --[[if item_pages_count > cfg.COLLAPSE_TABLE then
        table_class = 'wikitable mw-collapsible mw-collapsed'
    end--]]
    tbl:attr('class', table_class)
    
    --
    -- Header
    -- 
    local tr = tbl:tag('tr')
    if data_type == 'ingredient' then
        tr
            :tag('th')
                :wikitext(i18n.recipe_table.result)
    end
    
    tr
        :tag('th')
            :wikitext(i18n.recipe_table.part_amount)
            :done()
        :tag('th')
            :wikitext(i18n.recipe_table.part)
            :done()
    if group_notes then
        tr
            :tag('th')
                :wikitext(i18n.recipe_table.part_notes)
                :done()
    end
    if set_notes then
         tr
            :tag('th')
                :wikitext(i18n.recipe_table.description)
                :done()
    end
    tr
        :tag('th')
            :wikitext(i18n.recipe_table.meta)
            :done()
    --
    -- Rows
    --
    
    for _, set_key in ipairs(set_order) do
        local set = sets[set_key]
        
        if #set.groups > 0 then
            tr = tbl:tag('tr')
            tr:attr('class', 'upgraded-from-set')
            
            if data_type == 'ingredient' then
                local str
                if tpl_args.no_link == nil and item_pages_count < cfg.MAX_ITEMS then
                    local item_data = item_pages[set['acquisition_recipes._pageName']][1]
                    str = h.item_link{page=set['acquisition_recipes._pageName'], name=item_data['items.name'], inventory_icon=item_data['items.inventory_icon'] or '', html=item_data['items.html'] or '', skip_query=true}
                else
                    str = string.format('[[%s]]', set['acquisition_recipes._pageName'])
                end
                
                tr
                    :tag('td')
                        :attr('rowspan', #set.groups)
                        :wikitext(str)
                        :done()
            end
            
            local tr2
            for i, group in ipairs(set.groups) do
                if i <= 1 then
                    tr2 = tr
                else
                    tr2 = tbl:tag('tr')
                end
                local str
                if tpl_args.no_link == nil and item_pages_count < cfg.MAX_ITEMS then
                    local item_data = item_pages[group['acquisition_recipe_parts.item_page']][1]
                    str = h.item_link{
                        page=group['acquisition_recipe_parts.item_page'], 
                        name=item_data['items.name'], 
                        inventory_icon=item_data['items.inventory_icon'] or '', 
                        html=item_data['items.html'] or '', 
                        skip_query=true
                    }
                else
                    str = string.format('[[%s]]', group['acquisition_recipe_parts.item_page'])
                end
                
                tr2
                    :tag('td')
                        :attr('data-sort-type', 'number')
                        :wikitext(group['acquisition_recipe_parts.amount'])
                        :done()
                    :tag('td')
                        :wikitext(str)
                        :done()
                
                if group_notes then
                    if group['acquisition_recipe_parts.notes'] then
                        tr2
                            :tag('td')
                                :wikitext(group['acquisition_recipe_parts.notes'])
                    else
                        tr2
                            :node(
                                m_util.html.table_cell('na')
                            )
                    end
                end
            end
            
            if set_notes then
                if set['acquisition_recipes.description'] then
                    tr
                        :tag('td')
                            :attr('rowspan', #set.groups)
                            :wikitext(set['acquisition_recipes.description'])
                else
                    tr
                        :node(
                            m_util.html.table_cell('na')
                                :attr('rowspan', #set.groups)
                        )
                end
            end
            
            local t
            if set['acquisition_recipes.automatic'] == '1' then
                t = i18n.recipe_table.automatic
            else
                t = i18n.recipe_table.manual
            end
            
            tr
                :tag('td')
                    :attr('rowspan', #set.groups)
                    :wikitext(t)
        end
    end
    
    
    --
    -- print
    --
    
    if data_type == 'ingredient' then
        out[#out+1] = h.head(i18n.acquisition.recipe_usage_heading)
        out[#out+1] = string.format(i18n.acquisition.recipe_usage_text, item.name)
    else
        out[#out+1] = h.head(i18n.acquisition.recipes_heading)
        out[#out+1] = string.format(i18n.acquisition.recipes_text, item.name)
    end
    out[#out+1] = '<br>'
    out[#out+1] = tostring(tbl)
end

function h.reward_table(data, rtbl)
    if #data == 0 then
        return
    end
    
    local tbl = m_quest_reward.reward_tbl_head()
            
    for _, row in ipairs(data) do
        local classes
        if row[rtbl .. '.classes'] then
            classes = {}
            for _, class in ipairs(m_util.string.split(row[rtbl .. '.classes'], ',')) do
                classes[class] = true
            end
        end
        
        local tr = tbl:tag('tr')
        m_quest_reward.reward_tbl_row_head(tr, rtbl, row)

        if classes then
            for _, class in ipairs(m_game.constants.characters_order) do
                if classes[m_game.constants.characters[class].name] then
                    tr
                        :node(
                            m_util.html.table_cell('yes')
                        )
                else
                    tr
                        :node(
                            m_util.html.table_cell('no')
                        )
                end
            end
        else
            tr
                :node(
                    m_util.html.table_cell('yes')
                        :attr('colspan', 7)
                )
        end
    end
    
    return h.head(i18n.quest_reward[rtbl .. '_header']) .. i18n.quest_reward[rtbl .. '_intro'] .. tostring(tbl)
end

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

local function _main(tpl_args)
    --[[
    Duplicates the information from the infobox in a more readable 
    manner on the page.
    
    Example
    -------
    = p.item_acquisition{page='The Rite of Elements'}
    ]]

    local out = {}
    out[#out+1] = tpl_args.acquisition_insert
    
    -- fetch general item drop information that is used in multiple places in a
    -- single query to reduce performance hit
    local tables = {'items'}
    local fields = {
        'items._pageID=_pageID',
        'items._pageName=_pageName',
        'items.name=name',
        'items.class_id=class_id',
        'items.rarity_id=rarity_id',
        'items.base_item_page=base_item_page',
        'items.drop_text=drop_text',
        'items.acquisition_tags=acquisition_tags',
        'items.drop_areas=drop_areas',
        'items.drop_monsters=drop_monsters',
        'items.drop_enabled=drop_enabled',
        'items.is_drop_restricted=is_drop_restricted',
    }
    local query = {
        limit = 1,
    }
    if tpl_args.page then
        -- Join with _pageData in order to check for page redirect
        tables[#tables+1] = '_pageData'
        query.join = 'items._pageName = _pageData._pageNameOrRedirect'
        query.where = string.format(
            '_pageData._pageName="%s"',
            tpl_args.page
        )
    else
        query.where = string.format(
            'items._pageName="%s"',
            tostring(mw.title.getCurrentTitle())
        )
    end
    local item_data = m_cargo.query(tables, fields, query)
    if #item_data > 0 then
        item_data = item_data[1]
    end
    if item_data == nil then
        error("Item search unexpectedly returned no results")
    elseif item_data.name == nil then
        error("Item search result unexpectedly has no name")
    end
    
    -- ------------------------------------------------------------------------
    -- General drop restrictions
    -- ------------------------------------------------------------------------
    local drop_enabled = m_util.cast.boolean(item_data.drop_enabled)
    local drop_restricted = m_util.cast.boolean(item_data.is_drop_restricted)
    local is_unique = item_data.rarity_id == 'unique'
    if not drop_enabled then
        out[#out+1] = string.format(i18n.acquisition.drop_disabled, item_data.name)
    elseif drop_restricted then
        -- Acquisition unkown
        if not item_data.drop_areas and not item_data.drop_monsters and not item_data.drop_text then
            -- Divination cards
            if item_data.class_id == 'DivinationCard' then
                out[#out+1] = mw.getCurrentFrame():expandTemplate{title = i18n.templates.divination_card_acquisition_unknown}
                out[#out+1] = '\n\n'
            end
        end

        out[#out+1] = string.format(i18n.acquisition.drop_restricted, item_data.name)
        if is_unique then
            out[#out+1] = ' '
            out[#out+1] = i18n.acquisition.cannot_be_chanced
        end
    else
        if not item_data.drop_areas then
            out[#out+1] = string.format(i18n.acquisition.drop_anywhere, item_data.name)
        end
        if is_unique then
            local base_item_data = m_cargo.query(
                {'items'},
                {
                    'items.is_corrupted=is_corrupted',
                    'items.drop_enabled=drop_enabled',
                },
                {
                    where = string.format('items._pageName="%s"', item_data.base_item_page),
                    groupBy = 'items._pageID',
                }
            )
            if #base_item_data > 0 then
                base_item_data = base_item_data[1]
                if m_util.cast.boolean(base_item_data.drop_enabled) and not m_util.cast.boolean(base_item_data.is_corrupted) then
                    out[#out+1] = ' '
                    out[#out+1] = i18n.acquisition.can_be_chanced
                end
            end
        end
    end
    
    -- ------------------------------------------------------------------------
    -- League-specific
    -- ------------------------------------------------------------------------
    local acquisition_tags = {}
    if item_data.acquisition_tags then
        acquisition_tags = m_util.cast.table(item_data.acquisition_tags)
    end
    if m_util.table.contains(acquisition_tags, 'league-specific') and drop_enabled and is_unique then
        out[#out+1] = '\n\n'
        out[#out+1] = string.format(i18n.acquisition.league_specific_unique, item_data.name)
    end
    
    -- ------------------------------------------------------------------------
    -- Drop restrictions by text
    -- ------------------------------------------------------------------------
    if item_data.drop_text and drop_enabled then
        out[#out+1] = '\n\n'
        out[#out+1] = item_data.drop_text
    end
    
    -- ------------------------------------------------------------------------
    -- Drop restrictions by area
    -- ------------------------------------------------------------------------
    local drop_areas = {}
    if item_data.drop_areas then
        drop_areas = m_util.cast.table(item_data.drop_areas)
    end
    if #drop_areas > 0 then
        local results = m_cargo.query(
            {'areas'},
            {
                'areas._pageName=_pageName',
                'areas.id=id',
                'areas.name=name',
                'areas.main_page=main_page',
            },
            {
                where = string.format('areas.id IN ("%s") AND NOT areas.is_legacy_map_area', table.concat(drop_areas, '","')),
                orderBy = 'areas.name ASC',
                groupBy = 'areas._pageID',
            }
        )
        if #results > 0 then
            local ul = mw.html.create('ul')
            for _, row in ipairs(results) do
                ul:tag('li')
                    :wikitext(m_util.html.wikilink(row.main_page or row._pageName, row.name))
            end
            out[#out+1] = h.head(i18n.acquisition.area_header)
            out[#out+1] = i18n.acquisition.area
            out[#out+1] = tostring(ul)
        end
    end
    
    -- ------------------------------------------------------------------------
    -- Drop restrictions by monster
    -- ------------------------------------------------------------------------
    local monster_metadata_ids = {}
    if item_data.drop_monsters then
        monster_metadata_ids = m_util.cast.table(item_data.drop_monsters)
    end
            
    if #monster_metadata_ids > 0 then
        local results = m_cargo.query(
            {'monsters', 'main_pages'},
            {
                'monsters._pageName',
                'monsters.metadata_id',
                'monsters.name',
                -- 'monsters.main_page',
                'main_pages._pageName',
            },
            {
                join='monsters.metadata_id=main_pages.id',
                where=string.format('monsters.metadata_id IN ("%s")', table.concat(monster_metadata_ids, '","')),
                orderBy='monsters.name',
                groupBy='monsters.metadata_id',
            }
        )
        if #results > 0 then
            local ul = mw.html.create('ul')
            for _, row in ipairs(results) do
                ul:tag('li')
                    :wikitext(string.format(
                        '[[%s|%s]]', 
                           row['monsters.main_page'] 
                        or row['main_pages._pageName'] 
                        or row['monsters._pageName'], 
                        row['monsters.name']
                    ))
            end
            out[#out+1] = h.head(i18n.acquisition.monster_header)
            out[#out+1] = i18n.acquisition.monster
            out[#out+1]= '<br>'
            out[#out+1] = tostring(ul)
        end
    end
    
    -- ------------------------------------------------------------------------
    -- Recipes section
    -- ------------------------------------------------------------------------
    
    --
    -- Query recipe data
    --
    local obtained_sets, obtained_sets_order = h.fetch_recipes(
        string.format('acquisition_recipes._pageID="%s"', item_data._pageID),
        string.format('acquisition_recipe_parts._pageID="%s"', item_data._pageID)
    )
    
    -- Find recipes that item is used in
    local tables = {'acquisition_recipe_parts'}
    local fields = {
        '_pageID',
        'recipe_id',
    }
    local query = {
        where = string.format('item_page="%s"', item_data._pageName),
        groupBy = '_pageID, recipe_id',
    }

    -- Namespace condition
    -- This is mainly to prevent items from user pages or other testing pages 
    -- from being returned in the query results.
    if tpl_args.namespaces ~= 'any' then
        local namespaces = m_util.cast.table(tpl_args.namespaces, {callback=m_util.cast.number})
        if #namespaces > 0 then
            namespaces = table.concat(namespaces, ',')
        else
            namespaces = m_item_util.get_item_namespaces{format = 'list'}
        end
        query.where = string.format('%s AND _pageNamespace IN (%s)', query.where, namespaces)
    end

    local results = m_cargo.query(tables, fields, query)
    local recipes = {}
    local parts = {}
    for _, row in ipairs(results) do
        recipes[#recipes+1] = string.format('acquisition_recipes._pageID="%s" AND acquisition_recipes.recipe_id="%s"', row._pageID, row.recipe_id)
        parts[#parts+1] = string.format('acquisition_recipe_parts._pageID="%s" AND acquisition_recipe_parts.recipe_id="%s"', row._pageID, row.recipe_id)
    end
    parts = table.concat(parts, ' OR ')
    recipes = table.concat(recipes, ' OR ')

    local ingredient_sets, ingredient_sets_order = h.fetch_recipes(recipes, parts)
    --
    -- Query bulk item info for item linking
    --
    local item_pages = {assoc={}}
    for _, set_data in ipairs({{obtained_sets, obtained_sets_order}, {ingredient_sets, ingredient_sets_order}}) do
        for _, set_key in ipairs(set_data[2]) do
            local set = set_data[1][set_key]
            for _, group in ipairs(set.groups) do
                item_pages.assoc[group['acquisition_recipe_parts.item_page']] = true
            end
            item_pages.assoc[set['acquisition_recipes._pageName']] = true
        end
    end
    -- remove duplicates
    for name, _ in pairs(item_pages.assoc) do
        item_pages[#item_pages+1] = name
    end
    
    if #item_pages < cfg.MAX_ITEMS then
        item_pages = m_cargo.map_results_to_id{
            results=m_cargo.array_query{
                tables={'items'},
                fields={'items.name', 'items.inventory_icon', 'items.html'},
                id_array = item_pages,
                id_field = 'items._pageName',
                query = {
                    limit=5000
                },
            },
            field='items._pageName',
            keep_id_field=true,
        }
    end
    
    -- 
    -- Output for being obtained via vendor recipe
    --
    
    h.recipe_table(tpl_args, item_data, obtained_sets, obtained_sets_order, 'obtained', out, item_pages)
    
    -- 
    -- Ingredient of vendor recipes/upgrades
    -- 
    
    h.recipe_table(tpl_args, item_data, ingredient_sets, ingredient_sets_order, 'ingredient', out, item_pages)
    
    out[#out+1] = tpl_args.ingredient_append
    
    -- ------------------------------------------------------------------------
    -- Obtained via quest or vendor reward
    -- ------------------------------------------------------------------------
    
    local quest_rewards = m_cargo.query(
        {'quest_rewards'},
        {
            'quest_rewards.quest',
            'quest_rewards.act',
            'quest_rewards.classes',
            'quest_rewards.sockets',
            'quest_rewards.item_level',
            'quest_rewards.rarity',
        },
        {
            where=string.format('quest_rewards._pageName="%s"', item_data._pageName),
            orderBy='quest_rewards.act ASC, quest_rewards.quest_id ASC',
        }
    )
    out[#out+1] = h.reward_table(quest_rewards, 'quest_rewards')
    
    local vendor_rewards = m_cargo.query(
        {'vendor_rewards'},
        {
            'vendor_rewards.quest',
            'vendor_rewards.act',
            'vendor_rewards.classes',
            'vendor_rewards.npc',
        },
        {
            where=string.format('vendor_rewards._pageName="%s"', item_data._pageName),
            orderBy='vendor_rewards.act ASC, vendor_rewards.quest_id ASC',
        }
    )
    out[#out+1] = h.reward_table(vendor_rewards, 'vendor_rewards')
    
    -- ------------------------------------
    -- output
    -- ------------------------------------
    
    local head = mw.html.create('h2')
    head:wikitext(i18n.acquisition.header .. i18n.acquisition.link)
    return tostring(head) .. table.concat(out)
end

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

local p = {}

--
-- Template: Item acquisition
--
p.main = m_util.misc.invoker_factory(_main, {
    wrappers = cfg.wrapper,
})

p.item_acquisition = p.main

return p