Module:Item acquisition
The above documentation is transcluded from Module:Item acquisition/doc.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------
local getArgs = require('Module:Arguments').getArgs
local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')
local m_quest_reward = require('Module:Quest reward')._shared
local m_game = mw.loadData('Module:Game')
local f_item_link = require('Module:Item link').item_link
local f_message_box = require('Module:Message box').main
-- ----------------------------------------------------------------------------
-- 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 = '[[File:Questionmark.png|right|24px|link=Path_of_Exile_Wiki:How_to_edit_item_acquisition]]',
drop_leagues = 'Following the [[Version 3.14.0#Unique Item Balance|version 3.14.0 changes]], it is unclear exactly which [[Drop-restricted item#League-specific items|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 [[Drop-restricted item#League-specific items|other ways]].',
drop_disabled = '%s is [[drop disabled]].',
drop_restricted = '%s has restrictions on where or how it can drop.',
drop_anywhere = '%s can drop anywhere.',
cannot_be_chanced = 'It cannot be [[Orb of Chance|chanced]].',
can_be_chanced = 'It can be [[Orb of Chance|chanced]].',
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<br>Notes',
recipe_notes = 'General<br>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 [[vendor]]s, 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
local 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
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 < c.MAX_ITEMS then
local 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] = '<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)
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.name',
'items.rarity_id',
'items.drop_text',
'items.drop_leagues',
'items.drop_areas__full',
'items.drop_monsters',
'items.drop_enabled',
'items.is_drop_restricted',
},
{
where=string.format('items._pageName="%s"', tpl_args.page),
limit=1,
}
)
if #item_data > 0 then
item_data = item_data[1]
end
-- ------------------------------------------------------------------------
-- General drop restrictions
-- ------------------------------------------------------------------------
local drop_enabled = m_util.cast.boolean(item_data['items.drop_enabled'])
local drop_restricted = m_util.cast.boolean(item_data['items.is_drop_restricted'])
local is_unique = item_data['items.rarity_id'] == 'unique'
if not drop_enabled then
out[#out+1] = string.format(i18n.acquisition.drop_disabled, item_data['items.name'])
elseif drop_restricted then
out[#out+1] = string.format(i18n.acquisition.drop_restricted, item_data['items.name'])
if is_unique then
out[#out+1] = ' '
out[#out+1] = i18n.acquisition.cannot_be_chanced
end
else
if not item_data['items.drop_areas__full'] then
out[#out+1] = string.format(i18n.acquisition.drop_anywhere, item_data['items.name'])
end
if is_unique then
out[#out+1] = ' '
out[#out+1] = i18n.acquisition.can_be_chanced
end
end
-- ------------------------------------------------------------------------
-- Drop restrictions by league
-- ------------------------------------------------------------------------
if item_data['items.drop_leagues'] and drop_enabled then
local text = string.format(i18n.acquisition.drop_leagues, item_data['items.drop_leagues'])
local mbox = f_message_box('ambox', {
type = 'notice',
text = tostring(text),
})
out[#out+1] = '\n\n'
out[#out+1] = mbox
end
-- ------------------------------------------------------------------------
-- Drop restrictions by text
-- ------------------------------------------------------------------------
if item_data['items.drop_text'] and drop_enabled then
out[#out+1] = '\n\n'
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|%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]= '<br>'
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|%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
-- ------------------------------------------------------------------------
-- 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