Module:Item acquisition: Difference between revisions
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: | ||
-- ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
-- | -- | ||
-- ---------------------------------------------------------------------------- | -- Module:Item acquisition | ||
-- | |||
-- This module implements Template:Item acquisition. | |||
------------------------------------------------------------------------------- | |||
require('Module:No globals') | |||
local m_util = require('Module:Util') | local m_util = require('Module:Util') | ||
local | 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 | |||
local | |||
-- | -- 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 | 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. | 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 = | local results = m_cargo.query( | ||
{' | {'acquisition_recipes'}, | ||
{ | { | ||
' | 'acquisition_recipes._pageName', | ||
' | 'acquisition_recipes.recipe_id', | ||
' | '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[' | sets[row['acquisition_recipes._pageName'] .. tonumber(row['acquisition_recipes.recipe_id'])] = row | ||
end | end | ||
results = | 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', | ||
}, | }, | ||
{ | { | ||
Line 88: | Line 88: | ||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
sets[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) | ||
-- 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 | 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 | |||
-- ---------------------------------------------------------------------------- | |||
function | 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 | |||
local | -- fetch general item drop information that is used in multiple places in a | ||
local | -- 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] = | -- ------------------------------------------------------------------------ | ||
-- 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 | ||
-- ------------------------------------------------------------------------ | -- ------------------------------------------------------------------------ | ||
if item_data.drop_text and drop_enabled then | |||
out[#out+1] = '\n\n' | |||
out[#out+1] = item_data.drop_text | |||
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 | |||
-- ------------------------------------------------------------------------ | |||
-- 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'}, | |||
results = | |||
{' | |||
{ | { | ||
' | 'monsters._pageName', | ||
' | 'monsters.metadata_id', | ||
' | 'monsters.name', | ||
' | -- 'monsters.main_page', | ||
'main_pages._pageName', | |||
}, | }, | ||
{ | { | ||
where=string.format(' | join='monsters.metadata_id=main_pages.id', | ||
where=string.format('monsters.metadata_id IN ("%s")', table.concat(monster_metadata_ids, '","')), | |||
orderBy= | orderBy='monsters.name', | ||
groupBy=' | 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[' | :wikitext(string.format( | ||
end | '[[%s|%s]]', | ||
out[#out+1] = h.head( | row['monsters.main_page'] | ||
out[#out+1] = i18n.acquisition. | 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: | ||
-- ------------------------------------------------------------------------ | -- ------------------------------------------------------------------------ | ||
-- | -- Recipes section | ||
-- ------------------------------------------------------------------------ | -- ------------------------------------------------------------------------ | ||
-- | -- | ||
-- Query | -- Query recipe data | ||
-- | -- | ||
local obtained_sets, obtained_sets_order = h. | local obtained_sets, obtained_sets_order = h.fetch_recipes( | ||
string.format(' | string.format('acquisition_recipes._pageID="%s"', item_data._pageID), | ||
string.format(' | 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 | |||
local | -- 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 | end | ||
where | query.where = string.format('%s AND _pageNamespace IN (%s)', query.where, namespaces) | ||
end | end | ||
local ingredient_sets, ingredient_sets_order = h. | |||
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[' | item_pages.assoc[group['acquisition_recipe_parts.item_page']] = true | ||
end | end | ||
item_pages.assoc[set[' | item_pages.assoc[set['acquisition_recipes._pageName']] = true | ||
end | end | ||
end | end | ||
Line 253: | Line 614: | ||
end | end | ||
item_pages = | 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 | -- Output for being obtained via vendor recipe | ||
-- | -- | ||
h.recipe_table(tpl_args, item_data, obtained_sets, obtained_sets_order, 'obtained', out, item_pages) | |||
-- | -- | ||
Line 297: | Line 640: | ||
-- | -- | ||
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') | |||
out[#out+1] = | 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 .. | head:wikitext(i18n.acquisition.header .. i18n.acquisition.link) | ||
return tostring(head) .. table.concat(out) | return tostring(head) .. table.concat(out) | ||
end | 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 | return p |
Latest revision as of 18:19, 23 October 2024
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.
-------------------------------------------------------------------------------
--
-- 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