Module:Modifier table
The above documentation is transcluded from Module:Modifier 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.
-- Module responsible for the old fashioned mod tables
local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local cargo = mw.ext.cargo
local p = {}
-- ----------------------------------------------------------------------------
-- 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 = {
mod_table = {
name = m_util.html.abbr('Name', 'Name of the modifier if available or its internal identifier instead'),
mod_group = m_util.html.abbr('Group', 'Only one modifier from the specified group can appear at a time under normal circumstances'),
mod_type = 'Type',
domain = '[[Modifiers#Mod_Domain|Domain]]',
generation_type = '[[Modifiers#Mod_Generation_Type|Generation Type]]',
required_level = '[[Image:Level_up_icon_small.png|link=|For generated item/monster modifiers the minimum item/monster level respectively. Some generation types may not require this condition to be met, however item level restrictions may be raised to 80% of this value.]]',
stat_text = m_util.html.abbr('Stats', 'Stats of the modifier and the range they can roll in (if applicable)'),
buff = m_util.html.abbr('Buff', 'ID of the buff granted and the values associated'),
granted_skill = m_util.html.abbr('Skill', 'ID of the skill granted'),
tags = '[[Tags]]',
iiq = m_util.html.abbr('IIQ', 'increased Quantity of Items found in this Area'),
iir = m_util.html.abbr('IIR', 'increased Rarity of Items found in this Area'),
pack_size = m_util.html.abbr('Pack<br>Size', 'Monster pack size'),
spawn_weights = m_util.html.abbr('Spawn Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
generation_weights = m_util.html.abbr('Generation Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
},
errors = {
--
-- Mod template
--
sell_price_duplicate_name = 'Do not specify a sell price item name multiple times. Adjust the amount instead.',
sell_price_missing_argument = 'Both %s and %s must be specified',
},
}
--
-- Helper/Utility functions
--
local h = {}
function h.query_weights(table_name, page_ids)
results = cargo.query(
string.format('mods,%s', table_name),
string.format('mods._pageID,%s.tag,%s.weight', table_name, table_name),
{
where=page_ids,
join=string.format('mods._pageID=%s._pageID', table_name),
orderBy=string.format('mods.id ASC,%s.ordinal ASC', table_name),
limit=5000,
}
)
if #results == 5000 then
error('Hit maximum cargo results')
end
return m_util.cargo.map_results_to_id{results=results, table_name='mods'}
end
-- ----------------------------------------------------------------------------
-- Template: Mod table
-- ----------------------------------------------------------------------------
local mod_table = {}
mod_table.data = {
{
arg = nil,
header = i18n.mod_table.name,
fields = {'mods._pageName', 'mods.id', 'mods.name'},
options = {
[3] = {
optional=true,
},
},
display = function(tpl_args, frame, tr, data)
local name
if data['mods.name'] then
name = data['mods.name']
else
name = data['mods.id']
end
tr
:tag('td')
:wikitext(string.format('[[%s|%s]]', data['mods._pageName'], name))
end,
order = 1000,
sort_type = 'text',
},
{
arg = 'domain',
header = i18n.mod_table.domain,
fields = {'mods.domain'},
display = function(tpl_args, frame, tr, data)
local value = data['mods.domain']
tr
:tag('td')
:attr('data-sort-value', value)
:wikitext(m_game.constants.mod.domains[tonumber(value)]['short_upper'])
end,
order = 2000,
sort_type = 'text',
},
{
arg = 'generation_type',
header = i18n.mod_table.generation_type,
fields = {'mods.generation_type'},
display = function(tpl_args, frame, tr, data)
local value = data['mods.generation_type']
tr
:tag('td')
:attr('data-sort-value', value)
:wikitext(m_game.constants.mod.generation_types[tonumber(value)]['short_upper'])
end,
order = 2001,
sort_type = 'text',
},
{
arg = {'group', 'mod_group'},
header = i18n.mod_table.mod_group,
fields = {'mods.mod_group'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(data['mods.mod_group'])
end,
order = 2002,
sort_type = 'text',
},
{
arg = {'mod_type'},
header = i18n.mod_table.mod_type,
fields = {'mods.mod_type'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(data['mods.mod_type'])
end,
order = 2003,
sort_type = 'text',
},
{
arg = {'level', 'required_level'},
header = i18n.mod_table.required_level,
fields = {'mods.required_level'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(data['mods.required_level'])
end,
order = 2004,
},
{
arg = {'stat_text'},
header = i18n.mod_table.stat_text,
fields = {'mods.stat_text'},
display = function(tpl_args, frame, tr, data)
local text
-- map display type shows this in another column, remove this text to avoid clogging up the list
if tpl_args.type == 'map' then
local texts = m_util.string.split(data['mods.stat_text'], '<br>')
local out = {}
local valid
for _, v in ipairs(texts) do
valid = true
for _, data in pairs(mod_table.stat_ids) do
if string.find(v, data.pattern) ~= nil then
valid = false
break
end
end
if valid then
table.insert(out, v)
end
end
text = table.concat(out, '<br>')
else
text = data['mods.stat_text']
end
tr
:tag('td')
:wikitext(text)
end,
order = 3000,
sort_type = 'text',
},
{
arg = 'buff',
header = i18n.mod_table.buff,
fields = {'mods.granted_buff_id', 'mods.granted_buff_value'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(string.format('%s %s', data['mods.granted_buff_id'], data['mods.granted_buff_value']))
end,
order = 4000,
sort_type = 'text',
},
{
arg = {'skill', 'granted_skill'},
header = i18n.mod_table.granted_skill,
fields = {'mods.granted_skill'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(data['mods.granted_skill'])
end,
order = 4001,
sort_type = 'text',
},
{
arg = {'tags'},
header = i18n.mod_table.tags,
fields = {'mods.tags'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(table.concat(m_util.string.split(data['mods.tags'], ','), ', '))
end,
order = 5000,
sort_type = 'text',
},
}
mod_table.stat_ids = {
['map_item_drop_quantity_+%'] = {
header = i18n.mod_table.iiq,
pattern = '%d+%% increased Quantity of Items found in this Area',
},
['map_item_drop_rarity_+%'] = {
header = i18n.mod_table.iir,
pattern = '%d+%% increased Rarity of Items found in this Area',
},
['map_pack_size_+%'] = {
header = i18n.mod_table.pack_size,
pattern = '%+%d+%% Monster pack size',
}
}
mod_table.weights = {'spawn_weights', 'generation_weights'}
function p.mod_table(frame)
tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
for _, key in ipairs(mod_table.weights) do
tpl_args[key] = m_util.cast.boolean(tpl_args[key])
end
if string.find(tpl_args.q_where, '%[%[') ~= nil then
error('SMW leftover in where clause')
end
local row_infos = {}
for _, row_info in ipairs(mod_table.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
-- sort the rows
table.sort(row_infos, function (a, b)
return (a.order or 0) < (b.order or 0)
end)
-- Set tables
local tables = 'mods'
if tpl_args.q_tables then
tables = tables .. ',' .. tpl_args.q_tables
end
-- Set required fields
local fields = {
'mods._pageID',
}
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 {}
fields[#fields+1] = field
end
end
-- Parse query arguments
local query = {
-- Workaround: fix duplicates
groupBy='mods._pageID',
}
for key, value in pairs(tpl_args) do
if string.sub(key, 0, 2) == 'q_' then
query[string.sub(key, 3)] = value
end
end
local results = cargo.query(
tables,
table.concat(fields, ','),
query
)
if #results == 0 then
if tpl_args.default ~= nil then
return tpl_args.default
else
return 'No results found'
end
end
-- this might be needed in other queries, currently not checking if it's actually needed
-- because performance impact should be neglible
local page_ids = {}
for _, row in ipairs(results) do
page_ids[#page_ids+1] = string.format('mods._pageID="%s"', row['mods._pageID'])
end
page_ids = table.concat(page_ids, ' OR ')
local weights = {}
for _, key in ipairs(mod_table.weights) do
if tpl_args[key] then
weights[key] = h.query_weights(key, page_ids)
end
end
local stats
if tpl_args.type == 'map' then
local query_stat_ids = {}
for k, _ in pairs(mod_table.stat_ids) do
query_stat_ids[#query_stat_ids+1] = string.format('mod_stats.id="%s"', k)
end
local stat_results = cargo.query(
'mods,mod_stats',
'mods._pageID,mod_stats.id,mod_stats.min,mod_stats.max',
{
where=string.format('(%s) AND (%s)', page_ids, table.concat(query_stat_ids, ' OR ')),
join='mods._pageID=mod_stats._pageID',
orderBy='mods.id ASC',
limit=5000,
}
)
if #stat_results == 5000 then
error('Hit maximum cargo results')
end
stats = m_util.cargo.map_results_to_id{results=stat_results, table_name='mods'}
-- In addition map stats to stat <-> min/max pairs
for page_id, rows in pairs(stats) do
local stat_id_map = {}
for _, row in ipairs(rows) do
stat_id_map[row['mod_stats.id']] = {min=tonumber(row['mod_stats.min']), max=tonumber(row['mod_stats.max'])}
end
stats[page_id] = stat_id_map
end
end
local tbl = mw.html.create('table')
tbl:attr('class', 'wikitable sortable modifier-table')
-- Header
local tr = tbl:tag('tr')
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
if tpl_args.type == 'map' then
for stat_id, data in pairs(mod_table.stat_ids) do
tr
:tag('th')
:attr('data-sort-type', 'number')
:wikitext(data.header)
:done()
end
end
for _, key in ipairs(mod_table.weights) do
if tpl_args[key] then
tr
:tag('th')
:wikitext(i18n.mod_table[key])
end
end
-- Body
for _, row in ipairs(results) do
tr = tbl:tag('tr')
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
if rowinfo.options[index].optional ~= true then
display = false
break
else
row[field] = nil
end
end
end
if display then
rowinfo.display(tpl_args, frame, tr, row, rowinfo.fields)
else
tr:wikitext(m_util.html.td.na())
end
end
if tpl_args.type == 'map' then
for stat_id, data in pairs(mod_table.stat_ids) do
local stat_data = stats[row['mods._pageID']]
if stat_data and stat_data[stat_id] then
local v = stat_data[stat_id]
local text
if v.min == v.max then
text = v.min
else
text = string.format('(%s to %s)', v.min, v.max)
end
tr
:tag('td')
:attr('data-sort-value', (v.min+v.max)/2)
:wikitext(string.format('%s%%', text))
:done()
else
tr:wikitext(m_util.html.td.na())
end
end
end
for _, key in ipairs(mod_table.weights) do
if tpl_args[key] then
local weight_out = {}
for _, wrow in ipairs(weights[key][row['mods._pageID']]) do
weight_out[#weight_out+1] = string.format('%s %s', wrow[key .. '.tag'], wrow[key .. '.weight'])
end
if #weight_out > 0 then
tr
:tag('td')
:wikitext(table.concat(weight_out, '<br>'))
:done()
else
tr:wikitext(m_util.html.td.na())
end
end
end
end
return tostring(tbl)
end
-- ----------------------------------------------------------------------------
-- Debug functions
-- ----------------------------------------------------------------------------
p.debug = {}
function p.debug.tbl_data(tbl)
keys = {}
for _, data in ipairs(mod_table.data) 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
for _, key in ipairs(mod_table.weights) do
keys[key] = 1
end
local out = {}
for key, _ in pairs(keys) do
out[#out+1] = string.format("['%s'] = '1'", key)
end
return table.concat(out, ', ')
end
-- ----------------------------------------------------------------------------
--
-- ----------------------------------------------------------------------------
return p