Module:Item acquisition

-- -- Imports --

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

-- -- Strings -- -- This section contains strings used by this module. -- Add new strings here instead of in-code directly, this will help other -- people to correct spelling mistakes easier and help with translation to -- other PoE wikis. -- -- TODO: Maybe move this out to a separate sub-page module local i18n = { acquisition = { header = 'Item acquisition', link = '', drop_leagues = 'Following the version 3.14.0 changes, it is unclear exactly which league-specific items have been added to the core drop pool. If this item does not exist in the core drop pool, then it can only drop in areas with the league flag for %s active. League-specific items can also be acquired in other ways.', drop_disabled = 'This item is drop disabled.', area_header = 'Area restrictions', area_legacy_header = 'Legacy area restrictions', area = 'This item can be acquired in the following areas:', monster = 'This item can be acquired from the following monsters:', monster_header = 'Monster restrictions', 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:', },   upgraded_from_table = { outcome = 'Outcome', ingredient = 'Ingredient', ingredient_amount = 'Amount', ingredient_notes = 'Ingredient Notes', recipe_notes = 'General Notes', type = 'Type', automatic = m_util.html.abbr('Automatic', 'Automatically added by the item template based on the item\'s attributes.'), manual = m_util.html.abbr('Manual', 'Manually added to the item\'s wiki page.'), old_data = m_util.html.abbr('Old data', 'Old data - please null-edit the item\'s wiki page.'), },   quest_reward = { quest_rewards_header = 'Quest reward', quest_rewards_intro = 'This item is given as a quest reward for the following quests:', vendor_rewards_header = 'Vendor reward', vendor_rewards_intro = 'This item can be bought at the listed NPC vendors, after completing the following quests:', }, }

-- -- Globals --

local c = {}

c.MAX_ITEMS = 100 c.COLLAPSE_TABLE = 30

-- -- Helper functions --

local h = {} function h.head(str, level) local head = mw.html.create('h' .. (level or 3)) head:wikitext(str) return tostring(head) end

function h.fetch_upgraded_from_sets(where_set, where_group) if where_set == "" or where_group == "" then return {}, {} end local sets = {} local results = m_cargo.query(       {'upgraded_from_sets'},        {            'upgraded_from_sets._pageName',            'upgraded_from_sets.set_id',            'upgraded_from_sets.text',            'upgraded_from_sets.automatic',        },        {            where=where_set,        }    ) for _, row in ipairs(results) do       row.groups = {} sets[row['upgraded_from_sets._pageName'] .. tonumber(row['upgraded_from_sets.set_id'])] = row end results = m_cargo.query(       {'upgraded_from_groups'},        {            'upgraded_from_groups._pageName',            'upgraded_from_groups.set_id',            'upgraded_from_groups.group_id',            'upgraded_from_groups.notes',            'upgraded_from_groups.amount',            'upgraded_from_groups.item_name',            'upgraded_from_groups.item_page',        },        {            where=where_group,        }    ) 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 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]['upgraded_from_sets.set_id']) < tonumber(sets[b]['upgraded_from_sets.set_id'])    end) return sets, sets_sort end

function h.upgraded_from_table(tpl_args, 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['upgraded_from_sets.text'] ~= nil then set_notes = true end if #set.groups > 0 then for i, group in ipairs(set.groups) do               if group['upgraded_from_groups.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 mw-collapsible mw-expanded' if item_pages_count > c.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.upgraded_from_table.outcome) end tr       :tag('th') :wikitext(i18n.upgraded_from_table.ingredient_amount) :done :tag('th') :wikitext(i18n.upgraded_from_table.ingredient) :done if group_notes then tr           :tag('th') :wikitext(i18n.upgraded_from_table.ingredient_notes) :done end if set_notes then tr           :tag('th') :wikitext(i18n.upgraded_from_table.recipe_notes) :done end tr       :tag('th') :wikitext(i18n.upgraded_from_table.type) :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 < c.MAX_ITEMS then item_data = item_pages[set['upgraded_from_sets._pageName']][1] str = 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} else str = string.format('%s', set['upgraded_from_sets._pageName']) end tr                   :tag('td') :attr('rowspan', #set.groups) :wikitext(str) :done end 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 < c.MAX_ITEMS then item_data = item_pages[group['upgraded_from_groups.item_page']][1] str = 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 }               else str = string.format('%s', group['upgraded_from_groups.item_page']) end tr2 :tag('td') :attr('data-sort-type', 'number') :wikitext(group['upgraded_from_groups.amount']) :done :tag('td') :wikitext(str) :done if group_notes then if group['upgraded_from_groups.notes'] then tr2 :tag('td') :wikitext(group['upgraded_from_groups.notes']) else tr2:node(m_util.html.td.na{as_tag=true}) end end end if set_notes then if set['upgraded_from_sets.text'] then tr                       :tag('td') :attr('rowspan', #set.groups) :wikitext(set['upgraded_from_sets.text']) else tr                       :tag('td') :attr('class', 'table-na') :attr('rowspan', #set.groups) :wikitext('N/A') :done end end local t           if set['upgraded_from_sets.automatic'] == nil then t = i18n.upgraded_from_table.old_data elseif set['upgraded_from_sets.automatic'] == '1' then t = i18n.upgraded_from_table.automatic else t = i18n.upgraded_from_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.ingredient_header) out[#out+1] = i18n.acquisition.ingredient else out[#out+1] = h.head(i18n.acquisition.upgraded_from_header) out[#out+1] = i18n.acquisition.upgraded_from end out[#out+1] = ' ' 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) local cell = { [0] = {               value = '✗', sort = 0, class = 'table-cell-xmark', },           [1] = {                value = '✓', sort = 1, class = 'table-cell-checkmark', },       }        if rtbl == 'quest_rewards' then local value = m_quest_reward.reward_tbl_extra_info(row) -- If there isn't any extra information the checkmark will do           if value ~= '' then cell[1].value = value cell[1].class = nil cell[1].css = 'text-align: center;' end end if classes then for _, class in ipairs(m_game.constants.characters_order) do               local cell_value if classes[m_game.constants.characters[class].name] then cell_value = cell[1] else cell_value = cell[0] end tr:tag('td') :attr('class', cell_value.class or '') :attr('style', cell_value.css or '') :attr('table-sort-value', cell_value.sort) :wikitext(cell_value.value) end else tr:tag('td') :attr('colspan', 7) :attr('class', cell[1].class or '') :attr('style', cell[1].css or '') :attr('table-sort-value', cell[1].sort) :wikitext(cell[1].value) end end return h.head(i18n.quest_reward[rtbl .. '_header']) .. i18n.quest_reward[rtbl .. '_intro'] .. tostring(tbl) end

-- -- Templates --

local p = {}

-- -- Template: Item acquisition -- function p.item_acquisition (frame) --   Duplicates the information from the infobox in a more readable     manner on the page.    Example    ---    = p.item_acquisition{page='The Rite of Elements'}

-- Get args local tpl_args = getArgs(frame, {       parentFirst = true    }) frame = m_util.misc.get_frame(frame) tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle) local out = {} local results local query 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 item_data = m_cargo.query(       {'items'},        {            'items.drop_text',            'items.drop_leagues',            'items.drop_areas__full',            'items.drop_monsters',            'items.drop_enabled',        },        {            where=string.format('items._pageName="%s"', tpl_args.page),            limit=1,        }    ) if #item_data > 0 then item_data = item_data[1] end --    -- Drop disabled item --    local drop_enabled = m_util.cast.boolean(item_data['items.drop_enabled']) if not drop_enabled then out[#out+1] = i18n.acquisition.drop_disabled out[#out+1] = ' ' end --    -- Drop restrictions by league --    if item_data['items.drop_leagues'] and drop_enabled then out[#out+1] = string.format(i18n.acquisition.drop_leagues, item_data['items.drop_leagues']) out[#out+1] = ' ' end --    -- Drop restrictions by text --    if item_data['items.drop_text'] and drop_enabled then out[#out+1] = item_data['items.drop_text'] end --    -- Drop restrictions by area --    local area_ids if item_data['items.drop_areas__full'] then area_ids = m_util.string.split(item_data['items.drop_areas__full'], ',') else area_ids = {} end -- Handle legacy areas: local legacy_area_ids = { -- 'MapWar%', 'MapAtlas%', 'Map2%', 'MapTier%', }   local condition_current = {} local condition_legacy = {} local order_legacy = {} for i,v in ipairs(legacy_area_ids) do        condition_current[#condition_current+1] = string.format('areas.id NOT LIKE "%s"', v)        condition_legacy[#condition_legacy+1] = string.format('areas.id LIKE "%s"', v)        order_legacy[#order_legacy+1] = string.format('WHEN areas.id LIKE "%s" THEN %d', v,i) end local query_sets = { {           header=i18n.acquisition.area_header, condition=string.format('%s', table.concat(condition_current, ' AND ')), order='areas.name ASC', },       {            header=i18n.acquisition.area_legacy_header, condition=string.format('%s', table.concat(condition_legacy, ' OR ')), order=string.format(               CASE                     %s                    ELSE %d                 END ASC,                 areas.name ASC           ,             table.concat(order_legacy, ' '),             #order_legacy+1            ), },   }    if #area_ids > 0 then for _, query_set in ipairs(query_sets) do           local results = m_cargo.query(                {'areas'},                {                    'areas._pageName',                    'areas.id',                    'areas.name',                    'areas.main_page',                },                {                    where=string.format('(%s) AND areas.id IN ("%s")', query_set.condition, table.concat(area_ids, '","')),                    orderBy=query_set.order,                    groupBy='areas.id',                }            ) if #results > 0 then local ul = mw.html.create('ul') for _, row in ipairs(results) do                   ul:tag('li') :wikitext(string.format('%s', row['areas.main_page'] or row['areas._pageName'], row['areas.name'])) end out[#out+1] = h.head(query_set.header) out[#out+1] = i18n.acquisition.area out[#out+1]= ' ' out[#out+1] = tostring(ul) end end end --    -- Drop restrictions by monster --

local monster_metadata_ids = {} if item_data['items.drop_monsters'] then monster_metadata_ids = m_util.string.split(item_data['items.drop_monsters'], ',%s*') 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', 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]= ' ' out[#out+1] = tostring(ul) end end --    -- Vendor recipes/upgrades handling --    --    -- Query set data --   local obtained_sets, obtained_sets_order = h.fetch_upgraded_from_sets(        string.format('upgraded_from_sets._pageName="%s"', tpl_args.page),        string.format('upgraded_from_groups._pageName="%s"', tpl_args.page)    ) results = m_cargo.query(       {'upgraded_from_groups'},        {            'upgraded_from_groups._pageID',            'upgraded_from_groups.set_id',        },        {            where=string.format('upgraded_from_groups.item_page="%s"', tpl_args.page),            -- only need one result set for the where clause            groupBy='upgraded_from_groups._pageID, upgraded_from_groups.set_id',        }    ) local where = {sets={}, groups={}} for key, data in pairs(where) do       for _, row in ipairs(results) do            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']) end where[key] = table.concat(data, ' OR ') end local ingredient_sets, ingredient_sets_order = h.fetch_upgraded_from_sets(where.sets, where.groups) --   -- 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['upgraded_from_groups.item_page']] = true end item_pages.assoc[set['upgraded_from_sets._pageName']] = true end end -- remove duplicates for name, _ in pairs(item_pages.assoc) do       item_pages[#item_pages+1] = name end if #item_pages < c.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.upgraded_from_table(tpl_args, obtained_sets, obtained_sets_order, 'obtained', out, item_pages) --    -- Ingredient of vendor recipes/upgrades --    h.upgraded_from_table(tpl_args, 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"', tpl_args.page),            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"', tpl_args.page),            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

-- -- Return --

return p