[dismiss]
The wiki is currently a work in progress. If you'd like to help out, please check the Community Portal and our getting started guide. Also, check out our sister project on poewiki.net.
Module:Item table: Difference between revisions
Jump to navigation
Jump to search
>Illviljan (Moved to Module:Cargo. Reduced number of cargo queries for the advanced fields. Added sextant radius field.) |
>Illviljan |
||
Line 1: | Line 1: | ||
-- Item table | -- Item table | ||
-- | -- | ||
-- Creates various item tables from cargo queries. | --.Creates various item tables from cargo queries. | ||
-- | -- | ||
-- | -- | ||
Line 8: | Line 8: | ||
-- * Handle table columns that can have multiple cargo rows, preferably | -- * Handle table columns that can have multiple cargo rows, preferably | ||
-- in one or two queries. Not per column AND row. | -- in one or two queries. Not per column AND row. | ||
-- * | -- * Cargo queries can now be offset. Remove any limits due to that. | ||
Line 19: | Line 18: | ||
local m_game = require('Module:Game') | local m_game = require('Module:Game') | ||
local f_item_link = require('Module:Item link').item_link | local f_item_link = require('Module:Item link').item_link | ||
local | |||
local cargo = mw.ext.cargo | |||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
Line 126: | Line 126: | ||
quest_rewards_row_format = '[[Act %s]] after [[%s]] with %s.', | quest_rewards_row_format = '[[Act %s]] after [[%s]] with %s.', | ||
quest_rewards_any_classes = 'any character', | quest_rewards_any_classes = 'any character', | ||
}, | }, | ||
Line 153: | Line 154: | ||
local h = {} | local h = {} | ||
h.tbl = {} | h.tbl = {} | ||
Line 692: | Line 677: | ||
arg = {'quest'}, | arg = {'quest'}, | ||
header = i18n.item_table.quest_rewards, | header = i18n.item_table.quest_rewards, | ||
fields = {' | fields = {'quest_rewards.reward'}, | ||
display = function(tr, data | display = function (tr, data) | ||
-- Workaround for when there is multiple rows of the | |||
-- same item: | |||
local results = m_util.cargo.query( | |||
{'quest_rewards'}, | |||
{'quest_rewards.act', 'quest_rewards.classes', 'quest_rewards.quest', 'quest_rewards.quest_id', 'quest_rewards.reward'}, | |||
{ | |||
-- join = 'items._pageName = quest_rewards.reward', | |||
where = string.format('quest_rewards.reward="%s"', data['items._pageName']), | |||
orderBy = 'quest_rewards.act, quest_rewards.quest', | |||
} | } | ||
) | |||
local tbl = {} | local tbl = {} | ||
for | for i,v in ipairs(results) do | ||
local classes = table.concat(m_util.string.split(v['quest_rewards.classes'] or '', '�'), ', ') | local classes = table.concat(m_util.string.split(v['quest_rewards.classes'] or '', '�'), ', ') | ||
if classes == '' or classes == nil then | if classes == '' or classes == nil then | ||
Line 718: | Line 700: | ||
tbl[#tbl+1] = string.format( | tbl[#tbl+1] = string.format( | ||
i18n.item_table.quest_rewards_row_format, | i18n.item_table.quest_rewards_row_format or '234', | ||
v['quest_rewards.act'], | v['quest_rewards.act'], | ||
v['quest_rewards.quest'], | v['quest_rewards.quest'], | ||
Line 725: | Line 707: | ||
end | end | ||
tr | |||
:tag('td') | |||
:attr('style', 'text-align:left') | |||
:wikitext(table.concat(tbl, '<br>')) | |||
end, | end, | ||
order = 16001, | order = 16001, | ||
Line 741: | Line 718: | ||
arg = {'vendor'}, | arg = {'vendor'}, | ||
header = i18n.item_table.vendor_rewards, | header = i18n.item_table.vendor_rewards, | ||
fields = {' | fields = {'vendor_rewards.reward'}, | ||
display = function(tr, data | display = function (tr, data) | ||
-- Workaround for when there is multiple rows of the | |||
-- same item: | |||
local results = m_util.cargo.query( | |||
{'vendor_rewards'}, | |||
{'vendor_rewards.act', 'vendor_rewards.classes', 'vendor_rewards.npc', 'vendor_rewards.quest', 'vendor_rewards.quest_id', 'vendor_rewards.reward'}, | |||
{ | |||
where = string.format('vendor_rewards.reward="%s"', data['items._pageName']), | |||
orderBy = 'vendor_rewards.act, vendor_rewards.quest', | |||
} | } | ||
) | |||
local | local vendor_text = {} | ||
for i,v in ipairs(results) do | |||
for | |||
local classes = table.concat(m_util.string.split(v['vendor_rewards.classes'] or '', '�'), ', ') | local classes = table.concat(m_util.string.split(v['vendor_rewards.classes'] or '', '�'), ', ') | ||
if classes == '' or classes == nil then | if classes == '' or classes == nil then | ||
Line 766: | Line 739: | ||
end | end | ||
vendor_text[#vendor_text+1] = string.format( | |||
i18n.item_table.vendor_rewards_row_format, | i18n.item_table.vendor_rewards_row_format, | ||
v['vendor_rewards.act'], | v['vendor_rewards.act'], | ||
Line 773: | Line 746: | ||
classes | classes | ||
) | ) | ||
end | end | ||
tr | |||
:tag('td') | |||
:attr('style', 'text-align:left') | |||
:wikitext(table.concat(vendor_text, '<br>')) | |||
end, | end, | ||
order = 17001, | order = 17001, | ||
Line 795: | Line 764: | ||
-- Can purchase costs have multiple currencies and rows? | -- Can purchase costs have multiple currencies and rows? | ||
-- Just switch to the same method as in sell_price then. | -- Just switch to the same method as in sell_price then. | ||
local tbl = { | local tbl = { | ||
string.format( | |||
'%sx %s', | |||
data['item_purchase_costs.amount'], | |||
f_item_link{data['item_purchase_costs.name']} | |||
) | |||
} | |||
tr | tr | ||
:tag('td') | :tag('td') | ||
Line 814: | Line 783: | ||
header = i18n.item_table.sell_price, | header = i18n.item_table.sell_price, | ||
fields = {'item_sell_prices.name', 'item_sell_prices.amount'}, | fields = {'item_sell_prices.name', 'item_sell_prices.amount'}, | ||
display = function(tr, data | display = function (tr, data) | ||
-- Workaround for when there is multiple rows of the | |||
-- same item: | |||
local results = m_util.cargo.query( | |||
{'item_sell_prices'}, | |||
{'item_sell_prices.name', 'item_sell_prices.amount'}, | |||
{ | |||
where = string.format( | |||
'item_sell_prices._pageName="%s"', | |||
data['items._pageName'] | |||
), | |||
orderBy = 'item_sell_prices.name', | |||
} | } | ||
) | |||
local tbl = {} | local tbl = {} | ||
for | for i,v in ipairs(results) do | ||
tbl[#tbl+1] = string.format( | tbl[#tbl+1] = string.format( | ||
'%sx %s', | '%sx %s', | ||
Line 838: | Line 806: | ||
) | ) | ||
end | end | ||
tr | tr | ||
:tag('td') | :tag('td') | ||
Line 845: | Line 814: | ||
sort_type = 'text', | sort_type = 'text', | ||
}, | }, | ||
} | |||
data_map.skill_gem_new = { | |||
{ | { | ||
arg = 'icon', | |||
arg = 'icon', | |||
header = i18n.item_table.support_gem_letter, | header = i18n.item_table.support_gem_letter, | ||
fields = {'skill_gems.support_gem_letter_html'}, | fields = {'skill_gems.support_gem_letter_html'}, | ||
Line 1,162: | Line 1,088: | ||
error('SMW leftover in where clause') | error('SMW leftover in where clause') | ||
end | end | ||
tpl_args.q_where = | tpl_args.q_where = m_util.cargo.replace_holds{string=tpl_args.q_where} | ||
local modes = { | local modes = { | ||
skill = { | skill = { | ||
Line 1,209: | Line 1,135: | ||
end | end | ||
end | end | ||
-- Parse stat arguments | -- Parse stat arguments | ||
local stat_columns = {} | local stat_columns = {} | ||
Line 1,253: | Line 1,179: | ||
end | end | ||
until i == nil | until i == nil | ||
for _, col_info in ipairs(stat_columns) do | for _, col_info in ipairs(stat_columns) do | ||
local row_info = { | local row_info = { | ||
Line 1,320: | Line 1,246: | ||
table.insert(row_infos, row_info) | table.insert(row_infos, row_info) | ||
end | end | ||
-- sort the rows | -- sort the rows | ||
table.sort(row_infos, function (a, b) | table.sort(row_infos, function (a, b) | ||
Line 1,337: | Line 1,263: | ||
'items.size_y', | 'items.size_y', | ||
} | } | ||
local skill_levels = {} | |||
-- | -- | ||
local prepend = { | local prepend = { | ||
q_groupBy=true, | q_groupBy=true, | ||
q_tables=true | |||
} | } | ||
Line 1,354: | Line 1,283: | ||
end | end | ||
for _, rowinfo in ipairs(row_infos) do | for _, rowinfo in ipairs(row_infos) do | ||
if type(rowinfo.fields) == 'function' then | if type(rowinfo.fields) == 'function' then | ||
Line 1,369: | Line 1,297: | ||
end | end | ||
end | end | ||
if #skill_levels > 0 then | if #skill_levels > 0 then | ||
fields[#fields+1] = 'skill.max_level' | fields[#fields+1] = 'skill.max_level' | ||
tables_assoc.skill = true | tables_assoc.skill = true | ||
end | |||
-- reformat the fields & tables so they can be retrieved correctly | |||
for index, field in ipairs(fields) do | |||
fields[index] = string.format('%s=%s', field, field) | |||
end | end | ||
local tables = {} | local tables = {} | ||
for table_name,_ in pairs(tables_assoc) do | for table_name,_ in pairs(tables_assoc) do | ||
tables[#tables+1] = table_name | tables[#tables+1] = table_name | ||
end | end | ||
-- take care of required joins according to the tables | |||
-- | |||
local joins = {} | local joins = {} | ||
for index, table_name in ipairs(tables) do | for index, table_name in ipairs(tables) do | ||
Line 1,405: | Line 1,329: | ||
end | end | ||
-- Needed to eliminate duplicates supplied via table joins | -- Needed to eliminate duplicates supplied via table joins | ||
query.groupBy = 'items._pageID' .. (query.groupBy or '') | query.groupBy = 'items._pageID' .. (query.groupBy or '') | ||
query.limit = tonumber(query.limit) | |||
local results = | if query.limit == nil then | ||
query.limit = c.query_default | |||
elseif query.limit > c.query_max then | |||
query.limit = c.query_max | |||
end | |||
local results = cargo.query( | |||
table.concat(tables,',') .. (query.tables or ''), | |||
table.concat(fields,','), | |||
query | |||
) | |||
if #results == 0 and tpl_args.default ~= nil then | if #results == 0 and tpl_args.default ~= nil then | ||
return tpl_args.default | return tpl_args.default | ||
Line 1,424: | Line 1,358: | ||
pages[#pages+1] = string.format('(skill_levels._pageID="%s" AND skill_levels.level IN (0, 1, %s))', row['items._pageID'], row['skill.max_level']) | pages[#pages+1] = string.format('(skill_levels._pageID="%s" AND skill_levels.level IN (0, 1, %s))', row['items._pageID'], row['skill.max_level']) | ||
end | end | ||
local temp = | local temp = m_util.cargo.query( | ||
{'skill_levels'}, | {'skill_levels'}, | ||
skill_levels, | skill_levels, | ||
Line 1,430: | Line 1,364: | ||
where=table.concat(pages, ' OR '), | where=table.concat(pages, ' OR '), | ||
groupBy='skill_levels._pageID, skill_levels.level', | groupBy='skill_levels._pageID, skill_levels.level', | ||
limit=5000, | |||
} | } | ||
) | ) | ||
Line 1,459: | Line 1,394: | ||
end | end | ||
local temp = | local temp = m_util.cargo.query( | ||
{'items', 'item_stats'}, | {'items', 'item_stats'}, | ||
{'item_stats._pageName', 'item_stats.id', 'item_stats.min', 'item_stats.max', 'item_stats.avg'}, | {'item_stats._pageName', 'item_stats.id', 'item_stats.min', 'item_stats.max', 'item_stats.avg'}, | ||
Line 1,467: | Line 1,402: | ||
-- Cargo workaround: avoid duplicates using groupBy | -- Cargo workaround: avoid duplicates using groupBy | ||
groupBy='items._pageID, item_stats.id', | groupBy='items._pageID, item_stats.id', | ||
limit=5000, | |||
} | } | ||
) | ) | ||
Line 1,482: | Line 1,418: | ||
results2.stats[row['item_stats._pageName']][row['item_stats.id']] = stat | results2.stats[row['item_stats._pageName']][row['item_stats.id']] = stat | ||
end | end | ||
end | |||
-- Cargo doesnt support offset yet | |||
if #temp == 5000 then | |||
--TODO: Cargo | |||
error('Stats > 5000') | |||
end | end | ||
end | end | ||
end | end | ||
local tbl = mw.html.create('table') | local tbl = mw.html.create('table') | ||
tbl:attr('class', 'wikitable sortable item-table') | tbl:attr('class', 'wikitable sortable item-table') | ||
-- | -- Header | ||
local tr = tbl:tag('tr') | local tr = tbl:tag('tr') | ||
tr | tr | ||
Line 1,509: | Line 1,447: | ||
end | end | ||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
tr = tbl:tag('tr') | tr = tbl:tag('tr') | ||
Line 1,540: | Line 1,477: | ||
for index, field in ipairs(rowinfo.fields) do | for index, field in ipairs(rowinfo.fields) do | ||
-- this will bet set to an empty value not nil confusingly | -- this will bet set to an empty value not nil confusingly | ||
if | if row[field] == '' then | ||
local opts = rowinfo.options[index] | local opts = rowinfo.options[index] | ||
if opts.optional ~= true and opts.skill_levels ~= true then | if opts.optional ~= true and opts.skill_levels ~= true then | ||
Line 1,557: | Line 1,494: | ||
end | end | ||
end | end | ||
cats = {} | cats = {} | ||
if #results == query.limit then | if #results == query.limit then | ||
cats[#cats+1] = i18n.categories.query_limit | cats[#cats+1] = i18n.categories.query_limit | ||
end | |||
if #results == c.query_max then | |||
cats[#cats+1] = i18n.categories.query_hard_limit | |||
end | end | ||
Line 1,567: | Line 1,508: | ||
end | end | ||
mw.logObject( | mw.logObject(os.clock() -t) | ||
return tostring(tbl) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug}) | return tostring(tbl) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug}) | ||
Line 1,594: | Line 1,535: | ||
tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle()) | tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle()) | ||
local results = | local results = m_util.cargo.query( | ||
{'maps'}, | {'maps'}, | ||
{'maps.area_id'}, | {'maps.area_id'}, | ||
Line 1,623: | Line 1,564: | ||
tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle()) | tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle()) | ||
local results = | local results = m_util.cargo.query( | ||
{'prophecies'}, | {'prophecies'}, | ||
{'prophecies.objective', 'prophecies.reward'}, | {'prophecies.objective', 'prophecies.reward'}, | ||
Line 1,688: | Line 1,629: | ||
local where_str = table.concat(where_tbl, ' OR ') | local where_str = table.concat(where_tbl, ' OR ') | ||
results = | results = m_util.cargo.query( | ||
{'items', 'maps'}, | {'items', 'maps'}, | ||
{ | { |
Revision as of 11:25, 10 February 2019
The item module provides functionality for creating item tables.
Implemented templates
This module implements the following templates:
The above documentation is transcluded from Module:Item table/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.
-- Item table
--
--.Creates various item tables from cargo queries.
--
--
-- Todo list
-- ---------
-- * Handle table columns that can have multiple cargo rows, preferably
-- in one or two queries. Not per column AND row.
-- * Cargo queries can now be offset. Remove any limits due to that.
-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------
local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local f_item_link = require('Module:Item link').item_link
local cargo = mw.ext.cargo
-- ----------------------------------------------------------------------------
-- Globals
-- ----------------------------------------------------------------------------
local c = {}
c.query_default = 50
c.query_max = 300
-- ----------------------------------------------------------------------------
-- 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.
local i18n = {
categories = {
-- maintenance cats
query_limit = 'Item tables hitting query limit',
query_hard_limit = 'Item tables hitting hard query limit',
no_results = 'Item tables without results',
},
-- Used by the item table
item_table = {
item = 'Item',
skill_gem = 'Skill gem',
physical_dps = m_util.html.abbr('pDPS', 'physical damage per second'),
fire_dps = m_util.html.abbr('Fire DPS', 'fire damage per second'),
cold_dps = m_util.html.abbr('Cold DPS', 'cold damage per second'),
lightning_dps = m_util.html.abbr('Light. DPS', 'lightning damage per second'),
chaos_dps = m_util.html.abbr('Chaos DPS', 'chaos damage per second'),
elemental_dps = m_util.html.abbr('eDPS', 'elemental damage (i.e. fire/cold/lightning) per second'),
poison_dps = m_util.html.abbr('Poison DPS', 'poison damage (i.e. physical/chaos) per second'),
dps = m_util.html.abbr('DPS', 'total damage (i.e. physical/fire/cold/lightning/chaos) per second'),
base_item = 'Base Item',
item_class = 'Item Class',
essence_level = 'Essence<br>Level',
drop_level = 'Drop<br>Level',
drop_enabled = m_util.html.abbr('Drop<br>Enabled', 'If an item is drop disabled, it can not be normally obtained, but still may be available under specific conditions (like trading via standard league or limited time events'),
drop_leagues = 'Drop Leagues',
drop_areas = 'Drop Areas',
drop_text = 'Additional<br>Drop Restrictions',
stack_size = 'Stack<br>Size',
stack_size_currency_tab = m_util.html.abbr('Tab<br>Stack<br>Size', 'Stack size in the currency stash tab'),
armour = m_util.html.abbr('AR', 'Armour'),
evasion = m_util.html.abbr('EV', 'Evasion Rating'),
energy_shield = m_util.html.abbr('ES', 'Energy Shield'),
block = m_util.html.abbr('Block', 'Chance to Block'),
damage = m_util.html.abbr('Damage', 'Colour coded damage'),
attacks_per_second = m_util.html.abbr('APS', 'Attacks per second'),
local_critical_strike_chance = m_util.html.abbr('Crit', 'Local weapon critical strike chance'),
flask_life = m_util.html.abbr('Life', 'Life regenerated over the flask duration'),
flask_mana = m_util.html.abbr('Mana', 'Mana regenerated over the flask duration'),
flask_duration = 'Duration',
flask_charges_per_use = m_util.html.abbr('Usage', 'Number of charges consumed on use'),
flask_maximum_charges = m_util.html.abbr('Capacity', 'Maximum number of flask charges held'),
item_limit = 'Limit',
jewel_radius = 'Radius',
map_tier = 'Map<br>Tier',
map_level = 'Map<br>Level',
map_guild_character = m_util.html.abbr('Char', 'Character for the guild tag'),
master_level_requirement = '[[Image:Level up icon small.png|link=|Master level]]',
master = 'Master',
master_favour_cost = 'Favour<br>Cost',
variation_count = 'Variations',
buff_effects = 'Buff Effects',
stats = 'Stats',
quality_stats = 'Stats per 1% [[Quality]]',
effects = 'Effect(s)',
flavour_text = 'Flavour Text',
prediction_text = 'Prediction',
help_text = 'Help Text',
seal_cost = m_util.html.abbr('Seal<br>Cost', 'Silver Coin cost of sealing this prophecies into an item'),
objective = 'Objective',
reward = 'Reward',
buff_icon = 'Buff<br>Icon',
quest_name = 'Quest',
quest_act = 'Quest<br>Act',
purchase_costs = m_util.html.abbr('Purchase Cost', 'Cost of purchasing an item of this type at NPC vendors. This does not indicate whether NPCs actually sell the item.'),
sell_price = m_util.html.abbr('Sell Price', 'Items or currency received when selling this item at NPC vendors. Certain vendor recipes may override this value.'),
-- Skills
support_gem_letter = m_util.html.abbr('L', 'Support gem letter.'),
skill_icon = 'Icon',
description = 'Description',
skill_critical_strike_chance = m_util.html.abbr('Crit', 'Critical Strike Chance'),
cast_time = m_util.html.abbr('Cast<br>Time', 'Casting time of the skill in seconds'),
damage_effectiveness = m_util.html.abbr('Dmg.<br>Eff.', 'Damage Effectiveness'),
mana_cost_multiplier = m_util.html.abbr('MCM', 'Mana cost multiplier - missing values indicate it changes on gem level'),
mana_cost = m_util.html.abbr('Mana', 'Mana cost'),
reserves_mana_suffix = m_util.html.abbr('R', 'reserves mana'),
vaal_souls_requirement = m_util.html.abbr('Souls', 'Vaal souls requirement (1.5x in part 2, 2x in maps)'),
stored_uses = m_util.html.abbr('Uses', 'Maximum number of stored uses'),
primary_radius = m_util.html.abbr('R1', 'Primary radius'),
secondary_radius = m_util.html.abbr('R2', 'Secondary radius'),
tertiary_radius = m_util.html.abbr('R3', 'Tertiary radius'),
vendor_rewards = m_util.html.abbr('Vendor rewards', 'Vendor rewards after quest completion'),
vendor_rewards_row_format = '[[Act %s]] after [[%s]] from [[%s]] with %s.',
vendor_rewards_any_classes = 'any character',
quest_rewards = m_util.html.abbr('Quest rewards', 'Rewards after quest completion'),
quest_rewards_row_format = '[[Act %s]] after [[%s]] with %s.',
quest_rewards_any_classes = 'any character',
},
prophecy_description = {
objective = 'Objective',
reward = 'Reward',
},
item_disambiguation = {
original='the original variant',
drop_enabled='the current drop enabled variant',
drop_disabled='a legacy variant',
known_release = ' that was introduced in [[%s|%s]]',
list_pattern='%s, %s%s.'
},
errors = {
generic_argument_parameter = 'Unrecognized %s parameter "%s"',
invalid_item_table_mode = 'Invalid mode for item table',
},
}
-- ----------------------------------------------------------------------------
-- Helper & utility functions
-- ----------------------------------------------------------------------------
local h = {}
h.tbl = {}
function h.tbl.range_fields(field)
return function()
local fields = {}
for _, partial_field in ipairs({'maximum', 'text', 'colour'}) do
fields[#fields+1] = string.format('%s_range_%s', field, partial_field)
end
return fields
end
end
h.tbl.display = {}
function h.tbl.display.na_or_val(tr, value, data)
return h.na_or_val(tr, value)
end
function h.tbl.display.seconds(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('%ss', value)
end)
end
function h.tbl.display.percent(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('%s%%', value)
end)
end
function h.tbl.display.wikilink(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('[[%s]]', value)
end)
end
h.tbl.display.factory = {}
function h.tbl.display.factory.value(args)
args.options = args.options or {}
return function(tr, data, fields, data2)
local values = {}
local fmt_values = {}
local sdata = data2.skill_levels[data['items._pageName']]
for index, field in ipairs(fields) do
local value = {
min=data[field],
max=data[field],
base=data[field],
}
if sdata then
value.min = value.min or sdata['0'][field] or sdata['1'][field]
value.max = value.max or sdata['0'][field] or sdata[data['skill.max_level']][field]
end
if value.min then
values[#values+1] = value.max
local opts = args.options[index] or {}
-- global colour is set, no overrides
if args.colour ~= nil then
opts.no_color = true
end
fmt_values[#fmt_values+1] = m_util.html.format_value(nil, nil, value, opts)
end
end
if #values == 0 then
tr:wikitext(m_util.html.td.na())
else
local td = tr:tag('td')
td:attr('data-sort-value', table.concat(values, ', '))
td:wikitext(table.concat(fmt_values, ', '))
if args.colour then
td:attr('class', 'tc -' .. args.colour)
end
end
end
end
function h.tbl.display.factory.range(args)
-- args: table
-- property
return function (tr, data, fields)
tr
:tag('td')
:attr('data-sort-value', data[string.format('%s_range_maximum', args.field)] or '0')
:attr('class', 'tc -' .. (data[string.format('%s_range_colour', args.field)] or 'default'))
:wikitext(data[string.format('%s_range_text', args.field)])
:done()
end
end
function h.tbl.display.factory.descriptor_value(args)
-- Arguments:
-- key
-- tbl
args = args or {}
return function (tpl_args, frame, value)
args.tbl = args.tbl or tpl_args
if args.tbl[args.key] then
value = m_util.html.abbr(value, args.tbl[args.key])
end
return value
end
end
-- ----------------------------------------------------------------------------
-- Data mappings
-- ----------------------------------------------------------------------------
local data_map = {}
-- for sort type see:
-- https://meta.wikimedia.org/wiki/Help:Sorting
data_map.generic_item = {
{
arg = 'base_item',
header = i18n.item_table.base_item,
fields = {'items.base_item', 'items.base_item_page'},
display = function(tr, data)
tr
:tag('td')
:attr('data-sort-value', data['items.base_item'])
:wikitext(string.format('[[%s|%s]]', data['items.base_item_page'], data['items.base_item']))
end,
order = 1000,
sort_type = 'text',
},
{
arg = 'class',
header = i18n.item_table.item_class,
fields = {'items.class'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 1001,
sort_type = 'text',
},
{
arg = 'essence',
header = i18n.item_table.essence_level,
fields = {'essences.level'},
display = h.tbl.display.factory.value{},
order = 2000,
},
{
arg = {'drop', 'drop_level'},
header = i18n.item_table.drop_level,
fields = {'items.drop_level'},
display = h.tbl.display.factory.value{},
order = 3000,
},
{
arg = 'stack_size',
header = i18n.item_table.stack_size,
fields = {'stackables.stack_size'},
display = h.tbl.display.factory.value{},
order = 4000,
},
{
arg = 'stack_size_currency_tab',
header = i18n.item_table.stack_size_currency_tab,
fields = {'stackables.stack_size_currency_tab'},
display = h.tbl.display.factory.value{},
order = 4001,
},
{
arg = 'level',
header = m_game.level_requirement.icon,
fields = h.tbl.range_fields('items.required_level'),
display = h.tbl.display.factory.range{field='items.required_level'},
order = 5000,
},
{
arg = 'ar',
header = i18n.item_table.armour,
fields = h.tbl.range_fields('armours.armour'),
display = h.tbl.display.factory.range{field='armours.armour'},
order = 6000,
},
{
arg = 'ev',
header =i18n.item_table.evasion,
fields = h.tbl.range_fields('armours.evasion'),
display = h.tbl.display.factory.range{field='armours.evasion'},
order = 6001,
},
{
arg = 'es',
header = i18n.item_table.energy_shield,
fields = h.tbl.range_fields('armours.energy_shield'),
display = h.tbl.display.factory.range{field='armours.energy_shield'},
order = 6002,
},
{
arg = 'block',
header = i18n.item_table.block,
fields = h.tbl.range_fields('shields.block'),
display = h.tbl.display.factory.range{field='shields.block'},
order = 6003,
},
--[[{
arg = 'physical_damage_min',
header = m_util.html.abbr('Min', 'Local minimum weapon damage'),
fields = h.tbl.range_fields('minimum physical damage'),
display = h.tbl.display.factory.range{field='minimum physical damage'},
order = 7000,
},
{
arg = 'physical_damage_max',
header = m_util.html.abbr('Max', 'Local maximum weapon damage'),
fields = h.tbl.range_fields('maximum physical damage'),
display = h.tbl.display.factory.range{field='maximum physical damage'},
order = 7001,
},]]--
{
arg = {'weapon', 'damage'},
header = i18n.item_table.damage,
fields = {'weapons.damage_html', 'weapons.damage_avg'},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['weapons.damage_avg'])
:wikitext(data['weapons.damage_html'])
end,
order = 8000,
},
{
arg = {'weapon', 'aps'},
header = i18n.item_table.attacks_per_second,
fields = h.tbl.range_fields('weapons.attack_speed'),
display = h.tbl.display.factory.range{field='weapons.attack_speed'},
order = 8001,
},
{
arg = {'weapon', 'crit'},
header = i18n.item_table.local_critical_strike_chance,
fields = h.tbl.range_fields('weapons.critical_strike_chance'),
display = h.tbl.display.factory.range{field='weapons.critical_strike_chance'},
order = 8002,
},
{
arg = {'physical_dps'},
header = i18n.item_table.physical_dps,
fields = h.tbl.range_fields('weapons.physical_dps'),
display = h.tbl.display.factory.range{field='weapons.physical_dps'},
order = 8100,
},
{
arg = {'lightning_dps'},
header = i18n.item_table.lightning_dps,
fields = h.tbl.range_fields('weapons.lightning_dps'),
display = h.tbl.display.factory.range{field='weapons.lightning_dps'},
order = 8101,
},
{
arg = {'cold_dps'},
header = i18n.item_table.cold_dps,
fields = h.tbl.range_fields('weapons.cold_dps'),
display = h.tbl.display.factory.range{field='weapons.cold_dps'},
order = 8102,
},
{
arg = {'fire_dps'},
header = i18n.item_table.fire_dps,
fields = h.tbl.range_fields('weapons.fire_dps'),
display = h.tbl.display.factory.range{field='weapons.fire_dps'},
order = 8103,
},
{
arg = {'chaos_dps'},
header = i18n.item_table.chaos_dps,
fields = h.tbl.range_fields('weapons.chaos_dps'),
display = h.tbl.display.factory.range{field='weapons.chaos_dps'},
order = 8104,
},
{
arg = {'elemental_dps'},
header = i18n.item_table.elemental_dps,
fields = h.tbl.range_fields('weapons.elemental_dps'),
display = h.tbl.display.factory.range{field='weapons.elemental_dps'},
order = 8105,
},
{
arg = {'poison_dps'},
header = i18n.item_table.poison_dps,
fields = h.tbl.range_fields('weapons.poison_dps'),
display = h.tbl.display.factory.range{field='weapons.poison_dps'},
order = 8106,
},
{
arg = {'dps'},
header = i18n.item_table.dps,
fields = h.tbl.range_fields('weapons.dps'),
display = h.tbl.display.factory.range{field='weapons.dps'},
order = 8107,
},
{
arg = 'flask_life',
header = i18n.item_table.flask_life,
fields = h.tbl.range_fields('flasks.life'),
display = h.tbl.display.factory.range{field='flasks.life'},
order = 9000,
},
{
arg = 'flask_mana',
header = i18n.item_table.flask_mana,
fields = h.tbl.range_fields('flasks.mana'),
display = h.tbl.display.factory.range{field='flasks.mana'},
order = 9001,
},
{
arg = 'flask',
header = i18n.item_table.flask_duration,
fields = h.tbl.range_fields('flasks.duration'),
display = h.tbl.display.factory.range{field='flasks.duration'},
order = 9002,
},
{
arg = 'flask',
header = i18n.item_table.flask_charges_per_use,
fields = h.tbl.range_fields('flasks.charges_per_use'),
display = h.tbl.display.factory.range{field='flasks.charges_per_use'},
order = 9003,
},
{
arg = 'flask',
header = i18n.item_table.flask_maximum_charges,
fields = h.tbl.range_fields('flasks.charges_max'),
display = h.tbl.display.factory.range{field='flasks.charges_max'},
order = 9004,
},
{
arg = 'item_limit',
header = i18n.item_table.item_limit,
fields = {'jewels.item_limit'},
display = h.tbl.display.factory.value{},
order = 10000,
},
{
arg = 'jewel_radius',
header = i18n.item_table.jewel_radius,
fields = {'jewels.radius_html'},
display = function (tr, data)
tr
:tag('td')
:wikitext(data['jewels.radius_html'])
end,
order = 10001,
},
{
arg = 'map_tier',
header = i18n.item_table.map_tier,
fields = {'maps.tier'},
display = h.tbl.display.factory.value{},
order = 11000,
},
{
arg = 'map_level',
header = i18n.item_table.map_level,
fields = {'maps.area_level'},
display = h.tbl.display.factory.value{},
order = 11010,
},
{
arg = 'map_guild_character',
header = i18n.item_table.map_guild_character,
fields = {'maps.guild_character'},
display = h.tbl.display.factory.value{},
order = 11020,
sort_type = 'text',
},
{
arg = {'doodad', 'master_level_requirement'},
header = i18n.item_table.master_level_requirement,
fields = {'hideout_doodads.level_requirement'},
display = h.tbl.display.factory.value{},
order = 11100,
},
{
arg = {'doodad', 'master'},
header = i18n.item_table.master,
fields = {'hideout_doodads.master'},
display = h.tbl.display.factory.value{},
order = 11101,
sort_type = 'text',
},
{
arg = {'doodad', 'master_favour_cost'},
header = i18n.item_table.master_favour_cost,
fields = {'hideout_doodads.favour_cost'},
display = h.tbl.display.factory.value{colour='currency'},
order = 11102,
},
{
arg = {'doodad', 'variation_count'},
header = i18n.item_table.variation_count,
fields = {'hideout_doodads.variation_count'},
display = h.tbl.display.factory.value{colour='mod'},
order = 11105,
},
{
arg = 'buff',
header = i18n.item_table.buff_effects,
fields = {'item_buffs.stat_text'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12000,
sort_type = 'text',
},
{
arg = 'stat',
header = i18n.item_table.stats,
fields = {'items.stat_text'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12001,
sort_type = 'text',
},
{
arg = 'description',
header = i18n.item_table.effects,
fields = {'items.description'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12002,
sort_type = 'text',
},
{
arg = 'flavour_text',
header = i18n.item_table.flavour_text,
fields = {'items.flavour_text'},
display = h.tbl.display.factory.value{colour='flavour'},
order = 12003,
sort_type = 'text',
},
{
arg = 'help_text',
header = i18n.item_table.help_text,
fields = {'items.help_text'},
display = h.tbl.display.factory.value{colour='help'},
order = 12005,
sort_type = 'text',
},
{
arg = {'prophecy', 'objective'},
header = i18n.item_table.objective,
fields = {'prophecies.objective'},
display = h.tbl.display.factory.value{},
order = 13002,
},
{
arg = {'prophecy', 'reward'},
header = i18n.item_table.reward,
fields = {'prophecies.reward'},
display = h.tbl.display.factory.value{},
order = 13001,
},
{
arg = {'prophecy', 'seal_cost'},
header = i18n.item_table.seal_cost,
fields = {'prophecies.seal_cost'},
display = h.tbl.display.factory.value{colour='currency'},
order = 13002,
},
{
arg = {'prediction_text'},
header = i18n.item_table.prediction_text,
fields = {'prophecies.prediction_text'},
display = h.tbl.display.factory.value{colour='value'},
order = 12004,
sort_type = 'text',
},
{
arg = 'buff_icon',
header = i18n.item_table.buff_icon,
fields = {'item_buffs.icon'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 14000,
sort_type = 'text',
},
{
arg = {'drop', 'drop_enabled'},
header = i18n.item_table.drop_enabled,
fields = {'items.drop_enabled'},
display = h.tbl.display.factory.value{},
order = 15000,
},
{
arg = {'drop', 'drop_leagues'},
header = i18n.item_table.drop_leagues,
fields = {'items.drop_leagues'},
display = function (tr, data)
tr
:tag('td')
:wikitext(table.concat(m_util.string.split(data['items.drop_leagues'], ','), '<br>'))
end,
order = 15001,
sort_type = 'text',
},
{
arg = {'drop', 'drop_areas'},
header = i18n.item_table.drop_areas,
fields = {'items.drop_areas_html'},
display = h.tbl.display.factory.value{},
order = 15002,
sort_type = 'text',
},
{
arg = {'drop', 'drop_text'},
header = i18n.item_table.drop_text,
fields = {'items.drop_text'},
display = h.tbl.display.factory.value{},
order = 15003,
sort_type = 'text',
},
{
arg = {'quest'},
header = i18n.item_table.quest_rewards,
fields = {'quest_rewards.reward'},
display = function (tr, data)
-- Workaround for when there is multiple rows of the
-- same item:
local results = m_util.cargo.query(
{'quest_rewards'},
{'quest_rewards.act', 'quest_rewards.classes', 'quest_rewards.quest', 'quest_rewards.quest_id', 'quest_rewards.reward'},
{
-- join = 'items._pageName = quest_rewards.reward',
where = string.format('quest_rewards.reward="%s"', data['items._pageName']),
orderBy = 'quest_rewards.act, quest_rewards.quest',
}
)
local tbl = {}
for i,v in ipairs(results) do
local classes = table.concat(m_util.string.split(v['quest_rewards.classes'] or '', '�'), ', ')
if classes == '' or classes == nil then
classes = i18n.item_table.quest_rewards_any_classes
end
tbl[#tbl+1] = string.format(
i18n.item_table.quest_rewards_row_format or '234',
v['quest_rewards.act'],
v['quest_rewards.quest'],
classes
)
end
tr
:tag('td')
:attr('style', 'text-align:left')
:wikitext(table.concat(tbl, '<br>'))
end,
order = 16001,
sort_type = 'text',
},
{
arg = {'vendor'},
header = i18n.item_table.vendor_rewards,
fields = {'vendor_rewards.reward'},
display = function (tr, data)
-- Workaround for when there is multiple rows of the
-- same item:
local results = m_util.cargo.query(
{'vendor_rewards'},
{'vendor_rewards.act', 'vendor_rewards.classes', 'vendor_rewards.npc', 'vendor_rewards.quest', 'vendor_rewards.quest_id', 'vendor_rewards.reward'},
{
where = string.format('vendor_rewards.reward="%s"', data['items._pageName']),
orderBy = 'vendor_rewards.act, vendor_rewards.quest',
}
)
local vendor_text = {}
for i,v in ipairs(results) do
local classes = table.concat(m_util.string.split(v['vendor_rewards.classes'] or '', '�'), ', ')
if classes == '' or classes == nil then
classes = i18n.item_table.vendor_rewards_any_classes
end
vendor_text[#vendor_text+1] = string.format(
i18n.item_table.vendor_rewards_row_format,
v['vendor_rewards.act'],
v['vendor_rewards.quest'],
v['vendor_rewards.npc'],
classes
)
end
tr
:tag('td')
:attr('style', 'text-align:left')
:wikitext(table.concat(vendor_text, '<br>'))
end,
order = 17001,
sort_type = 'text',
},
{
arg = {'price', 'purchase_cost'},
header = i18n.item_table.purchase_costs,
fields = {'item_purchase_costs.name', 'item_purchase_costs.amount'},
display = function (tr, data)
-- Can purchase costs have multiple currencies and rows?
-- Just switch to the same method as in sell_price then.
local tbl = {
string.format(
'%sx %s',
data['item_purchase_costs.amount'],
f_item_link{data['item_purchase_costs.name']}
)
}
tr
:tag('td')
:wikitext(table.concat(tbl, '<br>'))
end,
order = 18001,
sort_type = 'text',
},
{
arg = {'price', 'sell_price'},
header = i18n.item_table.sell_price,
fields = {'item_sell_prices.name', 'item_sell_prices.amount'},
display = function (tr, data)
-- Workaround for when there is multiple rows of the
-- same item:
local results = m_util.cargo.query(
{'item_sell_prices'},
{'item_sell_prices.name', 'item_sell_prices.amount'},
{
where = string.format(
'item_sell_prices._pageName="%s"',
data['items._pageName']
),
orderBy = 'item_sell_prices.name',
}
)
local tbl = {}
for i,v in ipairs(results) do
tbl[#tbl+1] = string.format(
'%sx %s',
v['item_sell_prices.amount'],
f_item_link{v['item_sell_prices.name']}
)
end
tr
:tag('td')
:wikitext(table.concat(tbl, '<br>'))
end,
order = 18002,
sort_type = 'text',
},
}
data_map.skill_gem_new = {
{
arg = 'icon',
header = i18n.item_table.support_gem_letter,
fields = {'skill_gems.support_gem_letter_html'},
display = h.tbl.display.factory.value{},
order = 1000,
sort_type = 'text',
},
{
arg = 'skill_icon',
header = i18n.item_table.skill_icon,
fields = {'skill.skill_icon'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 1001,
sort_type = 'text',
},
{
arg = {'stat', 'stat_text'},
header = i18n.item_table.stats,
fields = {'skill.stat_text'},
display = h.tbl.display.factory.value{},
order = 2000,
sort_type = 'text',
},
{
arg = {'quality', 'quality_stat_text'},
header = i18n.item_table.quality_stats,
fields = {'skill.quality_stat_text'},
display = h.tbl.display.factory.value{},
order = 2001,
sort_type = 'text',
},
{
arg = 'description',
header = i18n.item_table.description,
fields = {'skill.description'},
display = h.tbl.display.factory.value{},
order = 2100,
sort_type = 'text',
},
{
arg = 'level',
header = m_game.level_requirement.icon,
fields = h.tbl.range_fields('items.required_level'),
display = h.tbl.display.factory.range{field='items.required_level'},
order = 3004,
},
{
arg = 'crit',
header = i18n.item_table.skill_critical_strike_chance,
fields = {'skill_levels.critical_strike_chance'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
skill_levels = true,
},
}},
order = 4000,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = 'cast_time',
header = i18n.item_table.cast_time,
fields = {'skill.cast_time'},
display = h.tbl.display.factory.value{options = {
}},
order = 4001,
options = {
},
},
{
arg = 'dmgeff',
header = i18n.item_table.damage_effectiveness,
fields = {'skill_levels.damage_effectiveness'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
skill_levels = true,
},
}},
order = 4002,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = 'mcm',
header = i18n.item_table.mana_cost_multiplier,
fields = {'skill_levels.mana_multiplier'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
skill_levels = true,
},
}},
order = 5000,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = 'mana',
header = i18n.item_table.mana_cost,
fields = {'skill_levels.mana_cost', 'skill.has_percentage_mana_cost', 'skill.has_reservation_mana_cost'},
display = function (tr, data, fields, data2)
local appendix = ''
if m_util.cast.boolean(data['skill.has_percentage_mana_cost']) then
appendix = appendix .. '%%'
end
if m_util.cast.boolean(data['skill.has_reservation_mana_cost']) then
appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
end
h.tbl.display.factory.value{options = {
[1] = {
fmt='%d' .. appendix,
skill_levels = true,
},
}}(tr, data, {'skill_levels.mana_cost'}, data2)
end,
order = 5001,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = 'vaal',
header = i18n.item_table.vaal_souls_requirement,
fields = {'skill_levels.vaal_souls_requirement'},
display = h.tbl.display.factory.value{options = {
[1] = {
skill_levels = true,
},
}},
order = 6000,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = 'vaal',
header = i18n.item_table.stored_uses,
fields = {'skill_levels.vaal_stored_uses'},
display = h.tbl.display.factory.value{options = {
[1] = {
skill_levels = true,
},
}},
order = 6001,
options = {
[1] = {
skill_levels = true,
},
},
},
{
arg = 'radius',
header = i18n.item_table.primary_radius,
fields = {'skill.radius', 'skill.radius_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skill.radius'])
:wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_description'}(nil, nil, data['skill.radius']))
end,
order = 7000,
},
{
arg = 'radius',
header = i18n.item_table.secondary_radius,
fields = {'skill.radius_secondary', 'skill.radius_secondary_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skill.radius_secondary'])
:wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_secondary_description'}(nil, nil, data['skill.radius_secondary']))
end,
order = 7001,
},
{
arg = 'radius',
header = i18n.item_table.tertiary_radius,
fields = {'skill.radius_tertiary', 'skill.radius_tertiary_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skill.radius_tertiary'])
:wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_tertiary_description'}(nil, nil, data['skill.radius_tertiary']))
end,
order = 7002,
},
}
for i, attr in ipairs(m_game.constants.attribute_order) do
local attr_data = m_game.constants.attributes[attr]
table.insert(data_map.generic_item, 7, {
arg = attr_data.arg,
header = attr_data.icon,
fields = h.tbl.range_fields(string.format('items.required_%s', attr)),
display = h.tbl.display.factory.range{field=string.format('items.required_%s', attr)},
order = 5000+i,
})
table.insert(data_map.skill_gem_new, 1, {
arg = attr_data.arg,
header = attr_data.icon,
fields = {string.format('skill_gems.%s_percent', attr)},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data[string.format('skill_gems.%s_percent', attr)])
:wikitext('[[File:Yes.png|yes|link=]]')
end,
order = 3000+i,
})
end
-- ----------------------------------------------------------------------------
-- Invoke callables
-- ----------------------------------------------------------------------------
local p = {}
--
-- Template:Item table
--
function p.item_table(frame)
--[[
Creates a generic table for items.
Examples
--------
= p.item_table{
q_tables = 'items, vendor_rewards',
q_join = 'items.name = vendor_rewards.reward',
q_where= 'vendor_rewards.reward IS NOT NULL AND (items.class = "Active Skill Gems" OR items.class = "Support Skill Gems")',
vendor=1,
}
]]
local t = os.clock()
-- args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
if string.find(tpl_args.q_where, '%[%[') ~= nil then
error('SMW leftover in where clause')
end
tpl_args.q_where = m_util.cargo.replace_holds{string=tpl_args.q_where}
local modes = {
skill = {
data = data_map.skill_gem_new,
header = i18n.item_table.skill_gem,
},
item = {
data = data_map.generic_item,
header = i18n.item_table.item,
},
}
if tpl_args.mode == nil then
tpl_args.mode = 'item'
end
if modes[tpl_args.mode] == nil then
error(i18n.errors.invalid_item_table_mode)
end
local results2 = {
stats = {},
skill_levels = {},
}
local row_infos = {}
for _, row_info in ipairs(modes[tpl_args.mode].data) do
local enabled = false
if row_info.arg == nil then
enabled = true
elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then
enabled = true
elseif type(row_info.arg) == 'table' then
for _, argument in ipairs(row_info.arg) do
if m_util.cast.boolean(tpl_args[argument]) then
enabled = true
break
end
end
end
if enabled then
row_info.options = row_info.options or {}
row_infos[#row_infos+1] = row_info
end
end
-- Parse stat arguments
local stat_columns = {}
local query_stats = {}
local i = 0
repeat
i = i + 1
local prefix = string.format('stat_column%s_', i)
local col_info = {
header = tpl_args[prefix .. 'header'] or tostring(i),
format = tpl_args[prefix .. 'format'],
stat_format = tpl_args[prefix .. 'stat_format'] or 'separate',
order = tonumber(tpl_args[prefix .. 'order']) or (10000000 + i),
stats = {},
options = {},
}
local j = 0
repeat
j = j +1
local stat_info = {
id = tpl_args[string.format('%sstat%s_id', prefix, j)],
}
if stat_info.id then
col_info.stats[#col_info.stats+1] = stat_info
query_stats[stat_info.id] = {}
else
-- Stop iteration entirely if this was the first index but no stat was supplied. We assume that we stop in this case.
if j == 1 then
i = nil
end
-- stop iteration
j = nil
end
until j == nil
-- Don't add this column if no stats were provided.
if #col_info.stats > 0 then
stat_columns[#stat_columns+1] = col_info
end
until i == nil
for _, col_info in ipairs(stat_columns) do
local row_info = {
--arg
header = col_info.header,
fields = {},
display = function(tr, data, properties)
if col_info.stat_format == 'separate' then
local stat_texts = {}
local num_stats = 0
local vmax = 0
for _, stat_info in ipairs(col_info.stats) do
num_stats = num_stats + 1
-- stat results from outside body
local stat = (results2.stats[data['items._pageName']] or {})[stat_info.id]
if stat ~= nil then
stat_texts[#stat_texts+1] = m_util.html.format_value(tpl_args, frame, stat, {no_color=true})
vmax = vmax + stat.max
end
end
if num_stats ~= #stat_texts then
tr:wikitext(m_util.html.td.na())
else
local text
if col_info.format then
text = string.format(col_info.format, unpack(stat_texts))
else
text = table.concat(stat_texts, ', ')
end
tr:tag('td')
:attr('data-sort-value', vmax)
:attr('class', 'tc -mod')
:wikitext(text)
end
elseif col_info.stat_format == 'add' then
local total_stat = {
min = 0,
max = 0,
avg = 0,
}
for _, stat_info in ipairs(col_info.stats) do
local stat = (results2.stats[data['items._pageName']] or {})[stat_info.id]
if stat ~= nil then
for k, v in pairs(total_stat) do
total_stat[k] = v + stat[k]
end
end
end
if col_info.format == nil then
col_info.format = '%s'
end
tr:tag('td')
:attr('data-sort-value', total_stat.max)
:attr('class', 'tc -mod')
:wikitext(string.format(col_info.format, m_util.html.format_value(tpl_args, frame, total_stat, {no_color=true})))
else
error(string.format(i18n.errors.generic_argument_parameter, 'stat_format', col_info.stat_format))
end
end,
order = col_info.order,
}
table.insert(row_infos, row_info)
end
-- sort the rows
table.sort(row_infos, function (a, b)
return (a.order or 0) < (b.order or 0)
end)
-- Parse query arguments
local tables_assoc = {items=true}
local fields = {
'items._pageID',
'items._pageName',
'items.name',
'items.inventory_icon',
'items.html',
'items.size_x',
'items.size_y',
}
local skill_levels = {}
--
local prepend = {
q_groupBy=true,
q_tables=true
}
local query = {}
for key, value in pairs(tpl_args) do
if string.sub(key, 0, 2) == 'q_' then
if prepend[key] then
value = ',' .. value
end
query[string.sub(key, 3)] = value
end
end
for _, rowinfo in ipairs(row_infos) do
if type(rowinfo.fields) == 'function' then
rowinfo.fields = rowinfo.fields()
end
for index, field in ipairs(rowinfo.fields) do
rowinfo.options[index] = rowinfo.options[index] or {}
if rowinfo.options[index].skill_levels then
skill_levels[#skill_levels+1] = field
else
fields[#fields+1] = field
tables_assoc[m_util.string.split(field, '%.')[1]] = true
end
end
end
if #skill_levels > 0 then
fields[#fields+1] = 'skill.max_level'
tables_assoc.skill = true
end
-- reformat the fields & tables so they can be retrieved correctly
for index, field in ipairs(fields) do
fields[index] = string.format('%s=%s', field, field)
end
local tables = {}
for table_name,_ in pairs(tables_assoc) do
tables[#tables+1] = table_name
end
-- take care of required joins according to the tables
local joins = {}
for index, table_name in ipairs(tables) do
if table_name ~= 'items' then
joins[#joins+1] = string.format('items._pageID=%s._pageID', table_name)
end
end
if #joins > 0 and query.join then
query.join = table.concat(joins, ',') .. ',' .. query.join
elseif #joins > 0 and not query.join then
query.join = table.concat(joins, ',')
elseif #joins == 0 and query.join then
-- leave query.join as is
end
-- Needed to eliminate duplicates supplied via table joins
query.groupBy = 'items._pageID' .. (query.groupBy or '')
query.limit = tonumber(query.limit)
if query.limit == nil then
query.limit = c.query_default
elseif query.limit > c.query_max then
query.limit = c.query_max
end
local results = cargo.query(
table.concat(tables,',') .. (query.tables or ''),
table.concat(fields,','),
query
)
if #results == 0 and tpl_args.default ~= nil then
return tpl_args.default
end
if #results > 0 then
-- fetch skill level information
if #skill_levels > 0 then
skill_levels[#skill_levels+1] = 'skill_levels._pageName'
skill_levels[#skill_levels+1] = 'skill_levels.level'
local pages = {}
for _, row in ipairs(results) do
pages[#pages+1] = string.format('(skill_levels._pageID="%s" AND skill_levels.level IN (0, 1, %s))', row['items._pageID'], row['skill.max_level'])
end
local temp = m_util.cargo.query(
{'skill_levels'},
skill_levels,
{
where=table.concat(pages, ' OR '),
groupBy='skill_levels._pageID, skill_levels.level',
limit=5000,
}
)
-- map to results
for _, row in ipairs(temp) do
if results2.skill_levels[row['skill_levels._pageName']] == nil then
results2.skill_levels[row['skill_levels._pageName']] = {}
end
-- TODO: convert to int?
results2.skill_levels[row['skill_levels._pageName']][row['skill_levels.level']] = row
end
end
if #stat_columns > 0 then
local pages = {}
for _, row in ipairs(results) do
pages[#pages+1] = string.format('item_stats._pageID="%s"', row['items._pageID'])
end
local query_stat_ids = {}
for stat_id, _ in pairs(query_stats) do
query_stat_ids[#query_stat_ids+1] = string.format('item_stats.id="%s"', stat_id)
end
if tpl_args.q_where then
tpl_args.q_where = string.format(' AND (%s)', tpl_args.q_where)
else
tpl_args.q_where = ''
end
local temp = m_util.cargo.query(
{'items', 'item_stats'},
{'item_stats._pageName', 'item_stats.id', 'item_stats.min', 'item_stats.max', 'item_stats.avg'},
{
where=string.format('item_stats.is_implicit IS NULL AND (%s) AND (%s)', table.concat(query_stat_ids, ' OR '), table.concat(pages, ' OR ')),
join='items._pageID=item_stats._pageID',
-- Cargo workaround: avoid duplicates using groupBy
groupBy='items._pageID, item_stats.id',
limit=5000,
}
)
for _, row in ipairs(temp) do
local stat = {
min = tonumber(row['item_stats.min']),
max = tonumber(row['item_stats.max']),
avg = tonumber(row['item_stats.avg']),
}
if results2.stats[row['item_stats._pageName']] == nil then
results2.stats[row['item_stats._pageName']] = {[row['item_stats.id']] = stat}
else
results2.stats[row['item_stats._pageName']][row['item_stats.id']] = stat
end
end
-- Cargo doesnt support offset yet
if #temp == 5000 then
--TODO: Cargo
error('Stats > 5000')
end
end
end
local tbl = mw.html.create('table')
tbl:attr('class', 'wikitable sortable item-table')
-- Header
local tr = tbl:tag('tr')
tr
:tag('th')
:wikitext(modes[tpl_args.mode].header)
:done()
for _, row_info in ipairs(row_infos) do
tr
:tag('th')
:attr('data-sort-type', row_info.sort_type or 'number')
:wikitext(row_info.header)
:done()
end
for _, row in ipairs(results) do
tr = tbl:tag('tr')
local il_args = {
skip_query=true,
page=row['items._pageName'],
name=row['items.name'],
inventory_icon=row['items.inventory_icon'],
width=row['items.size_x'],
height=row['items.size_y'],
}
if tpl_args.no_html == nil then
il_args.html = row['items.html']
end
if tpl_args.large then
il_args.large = tpl_args.large
end
tr
:tag('td')
:wikitext(f_item_link(il_args))
:done()
for _, rowinfo in ipairs(row_infos) do
-- this has been cast from a function in an earlier step
local display = true
for index, field in ipairs(rowinfo.fields) do
-- this will bet set to an empty value not nil confusingly
if row[field] == '' then
local opts = rowinfo.options[index]
if opts.optional ~= true and opts.skill_levels ~= true then
display = false
break
else
row[field] = nil
end
end
end
if display then
rowinfo.display(tr, row, rowinfo.fields, results2)
else
tr:wikitext(m_util.html.td.na())
end
end
end
cats = {}
if #results == query.limit then
cats[#cats+1] = i18n.categories.query_limit
end
if #results == c.query_max then
cats[#cats+1] = i18n.categories.query_hard_limit
end
if #results == 0 then
cats[#cats+1] = i18n.categories.no_results
end
mw.logObject(os.clock() -t)
return tostring(tbl) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
end
-------------------------------------------------------------------------------
-- Map item drops
-------------------------------------------------------------------------------
function p.map_item_drops(frame)
--[[
Gets the area id from the map item and activates
Template:Area_item_drops.
Examples:
= p.map_item_drops{page='Underground River Map (War for the Atlas)'}
]]
-- 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 results = m_util.cargo.query(
{'maps'},
{'maps.area_id'},
{
where=string.format('maps._pageName="%s" AND maps.area_id IS NOT NULL', tpl_args.page),
-- Only need each page name once
groupBy='maps._pageName',
}
)
local id = ''
if #results > 0 then
id = results[1]['maps.area_id']
end
return frame:expandTemplate{ title = 'Area item drops', args = {area_id=id} }
end
-------------------------------------------------------------------------------
-- Prophecy description
-------------------------------------------------------------------------------
function p.prophecy_description(frame)
-- 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 results = m_util.cargo.query(
{'prophecies'},
{'prophecies.objective', 'prophecies.reward'},
{
where=string.format('prophecies._pageName="%s"', tpl_args.page),
-- Only need each page name once
groupBy='prophecies._pageName',
}
)
results = results[1]
local out = {}
if results['prophecies.objective'] then
out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.objective)
out[#out+1] = results['prophecies.objective']
end
if results['prophecies.reward'] then
out[#out+1] = string.format('<h2>%s</h2>', i18n.prophecy_description.reward)
out[#out+1] = results['prophecies.reward']
end
return table.concat(out, '\n')
end
-- ----------------------------------------------------------------------------
-- Item disambiguation
-- ----------------------------------------------------------------------------
function h.find_aliases(tpl_args)
--[[
This function queries items for an item name, then checks if it has
had any name changes then queries for that name as well.
]]
-- Get initial name:
tpl_args.name_list = {
tpl_args.name or m_util.string.split(
tostring(mw.title.getCurrentTitle()),
' %('
)
}
-- Query for items with similar name, repeat until no new names are
-- found.
local n
local results = {}
local hash = {}
repeat
local n_old = #tpl_args.name_list
-- Multiple HOLDS doesn't work. Using __FULL and REGEXP instead.
local where_tbl = {}
for _, item_name in ipairs(tpl_args.name_list) do
for _, prefix in ipairs({'', 'Shaped '}) do
where_tbl[#where_tbl+1] = string.format(
'(items.name_list__FULL REGEXP "(�|^)%s%s(�|$)")',
prefix,
item_name
)
end
end
local where_str = table.concat(where_tbl, ' OR ')
results = m_util.cargo.query(
{'items', 'maps'},
{
'items._pageName',
'items.name',
'items.name_list',
'items.release_version',
'items.removal_version',
'items.drop_enabled',
},
{
join='items._pageName=maps._pageName',
where=where_str,
groupBy='items._pageName',
orderBy='items.release_version DESC, items.removal_version DESC, items.name ASC, maps.area_id ASC',
}
)
-- Filter duplicates:
for i,v in ipairs(results) do
local r = m_util.string.split(v['items.name_list'], '�')
if type(r) == string then
r = {r}
end
for j,m in ipairs(r) do
if hash[m] == nil then
hash[m] = m
tpl_args.name_list[#tpl_args.name_list+1] = m
end
end
end
until #tpl_args.name_list == n_old
return results
end
function p.item_disambiguation(frame)
--[[
This function finds that items with a name or has had that name.
To do
-----
Should text imply which is the original map, even if it isn't (Original)?
How to properly sort drop disabled items, with removal version?
How to deal with names that have been used multiple times? Terrace Map
Examples
--------
= p.item_disambiguation{name='Abyss Map'}
= p.item_disambiguation{name='Caldera Map'}
= p.item_disambiguation{name='Crypt Map'}
= p.item_disambiguation{name='Catacombs Map'}
= p.item_disambiguation{name='Harbinger Map (High Tier)'}
]]
-- Get template arguments.
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
local current_title = tostring(mw.title.getCurrentTitle())
-- Get the page name.
tpl_args.name = tpl_args.name or m_util.string.split(
current_title,
' %('
)[1]
-- Query for items with similar name.
local results = h.find_aliases(tpl_args)
-- Format the results:
local out = {}
local container = mw.html.create('div')
local tbl = container:tag('ul')
for i,v in ipairs(results) do
if v['items._pageName'] ~= current_title then
-- Get the content inside the last parentheses:
local known_release = string.reverse(
string.match(
string.reverse(v['items._pageName']),
'%)(.-)%('
)
)
local drop_enabled
if known_release == 'Original' then
drop_enabled = i18n.item_disambiguation.original
elseif m_util.cast.boolean(v['items.drop_enabled']) then
drop_enabled = i18n.item_disambiguation.drop_enabled
else
drop_enabled = i18n.item_disambiguation.drop_disabled
end
if known_release ~= 'Original' then
known_release = string.format(
i18n.item_disambiguation.known_release,
known_release,
known_release
)
else
known_release = ''
end
tbl
:tag('li')
:wikitext(
string.format(
i18n.item_disambiguation.list_pattern,
f_item_link{page=v['items._pageName']},
drop_enabled,
known_release
)
)
end
end
out[#out+1] = tostring(container)
-- Add a category when the template uses old template inputs:
local old_args = {
'war',
'atlas',
'awakening',
'original',
'heading',
'hide_heading',
'show_current',
}
for _,v in ipairs(old_args) do
if tpl_args[v] ~= nil then
return table.concat(out, '') .. m_util.misc.add_category(
{'Pages with old template arguments'}
)
end
end
return table.concat(out, '')
end
-- ----------------------------------------------------------------------------
-- Debug stuff
-- ----------------------------------------------------------------------------
p.debug = {}
function p.debug._tbl_data(tbl)
keys = {}
for _, data in ipairs(tbl) do
if type(data.arg) == 'string' then
keys[data.arg] = 1
elseif type(data.arg) == 'table' then
for _, arg in ipairs(data.arg) do
keys[arg] = 1
end
end
end
local out = {}
for key, _ in pairs(keys) do
out[#out+1] = string.format("['%s'] = '1'", key)
end
return table.concat(out, ', ')
end
function p.debug.generic_item_all()
return p.debug._tbl_data(data_map.generic_item)
end
function p.debug.skill_gem_all()
return p.debug._tbl_data(data_map.skill_gem_new)
end
-- ----------------------------------------------------------------------------
-- Return
-- ----------------------------------------------------------------------------
return p