Module:Modifier table: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
(One intermediate revision by one other user not shown) | |||
Line 80: | Line 80: | ||
if #values == 0 then | if #values == 0 then | ||
tr | tr | ||
: | :node(m_util.html.table_cell('na')) | ||
else | else | ||
local td = tr:tag('td') | local td = tr:tag('td') | ||
Line 534: | Line 534: | ||
row_info.display(tpl_args, tr, row, display_fields[i]) | row_info.display(tpl_args, tr, row, display_fields[i]) | ||
else | else | ||
tr: | tr:node(m_util.html.table_cell('na')) | ||
end | end | ||
end | end | ||
Line 555: | Line 555: | ||
:done() | :done() | ||
else | else | ||
tr: | tr:node(m_util.html.table_cell('na')) | ||
end | end | ||
end | end | ||
Line 583: | Line 583: | ||
:done() | :done() | ||
else | else | ||
tr: | tr:node(m_util.html.table_cell('na')) | ||
end | end | ||
end | end | ||
Line 594: | Line 594: | ||
:wikitext(row[field]) | :wikitext(row[field]) | ||
else | else | ||
tr: | tr:node(m_util.html.table_cell('na')) | ||
end | end | ||
end | end |
Latest revision as of 15:16, 21 October 2024
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:Modifier table
--
-- Module responsible for displaying modifiers in various ways. Implements
-- Template:Modifier table and Template:Item modifiers
-------------------------------------------------------------------------------
require('Module:No globals')
local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Modifier table')
local m_game = mw.loadData('Module:Game')
-- 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:Modifier table/config/sandbox') or mw.loadData('Module:Modifier table/config')
local i18n = cfg.i18n
-- ----------------------------------------------------------------------------
-- Helper functions
-- ----------------------------------------------------------------------------
local h = {}
function h.query_weights(table_name, page_ids)
return m_cargo.map_results_to_id{
results=m_cargo.query(
{'mods', table_name},
{
'mods._pageID',
table_name .. '.tag',
table_name .. '.value'
},
{
where=page_ids,
join=string.format('mods._pageID=%s._pageID', table_name),
orderBy=string.format('mods.id ASC,%s.ordinal ASC', table_name),
}
),
field='mods._pageID',
}
end
h.tbl = {}
h.tbl.display = {}
h.tbl.display.factory = {}
function h.tbl.display.factory.value(args)
-- Format options for each field:
args.options = args.options or {}
-- Separator between fields:
args.delimiter = args.delimiter or ', '
return function(tpl_args, tr, data, fields)
local values = {}
local fmt_values = {}
for index, field in ipairs(fields) do
local value = {
min=data[field],
max=data[field],
base=data[field],
}
if value.min then
values[#values+1] = value.max
local opts = args.options[index] or {}
-- Global colour is set, no overrides.
if args.color ~= nil or opts.color == nil then
opts.no_color = true
end
fmt_values[#fmt_values+1] = m_util.html.format_value(nil, value, opts)
end
end
if #values == 0 then
tr
:node(m_util.html.table_cell('na'))
else
local td = tr:tag('td')
td
:attr('data-sort-value', table.concat(values, args.delimiter))
:wikitext(table.concat(fmt_values, args.delimiter))
if args.color then
td:attr('class', 'tc -' .. args.color)
end
end
end
end
-- ----------------------------------------------------------------------------
-- Additional configuration
-- ----------------------------------------------------------------------------
local mod_table = {}
mod_table.data = {
{
arg = 'name',
header = i18n.mod_table.name,
fields = {'mods._pageName', 'mods.id', 'mods.name'},
options = {
[3] = {
optional=true,
},
},
display = function(tpl_args, tr, data, fields)
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, tr, data, fields)
local k = 'mods.domain'
local i = tonumber(data[k])
if m_game.constants.mod.domains[i] == nil then
error('Undefined Modifier Domain ['..i..'] needs to be added to Module:Game')
end
data[k] = m_game.constants.mod.domains[i]['short_upper']
h.tbl.display.factory.value{}(tpl_args, tr, data, fields)
end,
order = 2000,
sort_type = 'text',
},
{
arg = 'generation_type',
header = i18n.mod_table.generation_type,
fields = {'mods.generation_type'},
display = function(tpl_args, tr, data, fields)
local k = 'mods.generation_type'
local i = tonumber(data[k])
if m_game.constants.mod.generation_types[i] == nil then
error('Undefined Modifier Generation Type ['..i..'] needs to be added to Module:Game')
end
data[k] = m_game.constants.mod.generation_types[i]['short_upper']
h.tbl.display.factory.value{}(tpl_args, tr, data, fields)
end,
order = 2001,
sort_type = 'text',
},
{
arg = {'group', 'mod_group'},
header = i18n.mod_table.mod_groups,
fields = {'mods.mod_groups'},
display = function(tpl_args, tr, data, fields)
local k = 'mods.mod_groups'
data[k] = table.concat(m_util.string.split(data[k], ',%s*'), ', ')
h.tbl.display.factory.value{}(tpl_args, tr, data, fields)
end,
order = 2002,
sort_type = 'text',
},
{
arg = {'mod_type'},
header = i18n.mod_table.mod_type,
fields = {'mods.mod_type'},
display = h.tbl.display.factory.value{},
order = 2003,
sort_type = 'text',
},
{
arg = {'level', 'required_level'},
header = i18n.mod_table.required_level,
fields = {'mods.required_level'},
display = h.tbl.display.factory.value{},
order = 2004,
},
{
arg = {'enchantment', 'labyrinth'},
header = i18n.mod_table.labyrinth,
fields = {string.format([[
CONCAT(
CASE mods.required_level
WHEN 32 THEN "%s"
WHEN 53 THEN "%s"
WHEN 66 THEN "%s"
WHEN 75 THEN "%s"
WHEN 83 THEN "%s"
END
)=labyrinth_text]],
i18n.mod_table.normal_labyrinth,
i18n.mod_table.cruel_labyrinth,
i18n.mod_table.merciless_labyrinth,
i18n.mod_table.eternal_labyrinth,
i18n.mod_table.belt_labyrinth)
},
display = h.tbl.display.factory.value{},
order = 2005,
sort_type = 'text',
},
{
arg = {'stat_text'},
header = i18n.mod_table.stat_text,
fields = {'mods.stat_text'},
display = function(tpl_args, tr, data, fields)
local value
-- 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
value = table.concat(out, '<br>')
else
value = data['mods.stat_text']
end
data['mods.stat_text'] = value
h.tbl.display.factory.value{color='mod'}(tpl_args, tr, data, fields)
end,
order = 3000,
sort_type = 'text',
},
{
arg = 'buff',
header = i18n.mod_table.buff,
fields = {'mods.granted_buff_id', 'mods.granted_buff_value'},
display = h.tbl.display.factory.value{delimiter=' '},
order = 4000,
sort_type = 'text',
},
{
arg = {'skill', 'granted_skill'},
header = i18n.mod_table.granted_skill,
fields = {'mods.granted_skill'},
display = h.tbl.display.factory.value{},
order = 4001,
sort_type = 'text',
},
{
arg = {'tags'},
header = i18n.mod_table.tags,
fields = {'mods.tags'},
display = function(tpl_args, tr, data, fields)
local k = 'mods.tags'
data[k] = table.concat(m_util.string.split(data[k], ',%s*'), ', ')
h.tbl.display.factory.value{}(tpl_args, tr, data, fields)
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'}
-- ----------------------------------------------------------------------------
-- Main functions
-- ----------------------------------------------------------------------------
local function _mod_table(tpl_args)
--[[
Creates a generic table for modifiers.
Examples
--------
= p.mod_table{
q_tables='mod_spawn_weights',
q_join='mods._pageID=mod_spawn_weights._pageID',
q_where='mods.generation_type = 10 AND mod_spawn_weights.tag = "boots" AND mod_spawn_weights.weight > 0',
q_orderBy='mods.id, mods.required_level',
q_limit=100,
stat_text=1,
enchantment=1,
}
]]
-- default to enabled
tpl_args.name = tpl_args.name or true
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 required and extra tables:
local tables = {'mods'}
for _, v in ipairs(m_util.string.split_args(tpl_args.q_tables, {',%s*'})) do
tables[#tables+1] = v
end
-- Set required and extra fields:
local fields = {
'mods._pageID',
}
for _, row_info in ipairs(row_infos) do
if type(row_info.fields) == 'function' then
row_info.fields = row_info.fields()
end
for index, field in ipairs(row_info.fields) do
row_info.options[index] = row_info.options[index] or {}
fields[#fields+1] = field
end
end
tpl_args._extra_fields = m_util.string.split_args(tpl_args.q_fields, {',%s*'})
for _, v in ipairs(tpl_args._extra_fields) do
fields[#fields+1] = v
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 = m_cargo.query(tables, 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('mod_' .. 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
stats = m_cargo.map_results_to_id{
results=m_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',
}
),
field='mods._pageID'
}
-- 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
--
-- Display
--
-- Preformance optimization
for index, field in ipairs(tpl_args._extra_fields) do
field = m_util.string.split(field, '%s*=%s*')
-- field[2] will be nil if there is no alias
tpl_args._extra_fields[index] = field[2] or field[1]
end
local tbl = mw.html.create('table')
tbl:attr('class', 'wikitable sortable modifier-table')
-- Header
local tr = tbl:tag('tr')
local display_fields = {}
for i, row_info in ipairs(row_infos) do
for j, field in ipairs(row_info.fields) do
-- Aliased name is used as keys in the results:
field = m_util.string.split(field, '%s*=%s*')
field = field[2] or field[1]
-- Make a new field table since mod_table.data will remain
-- modified the 2nd time a function is run and then crash.
if j == 1 then
display_fields[i] = {}
end
display_fields[i][j] = field
end
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
for _, field in ipairs(tpl_args._extra_fields) do
tr
:tag('th')
:wikitext(field)
end
-- Body
for _, row in ipairs(results) do
tr = tbl:tag('tr')
for i, row_info in ipairs(row_infos) do
-- this has been cast from a function in an earlier step
local display = true
for j, field in ipairs(display_fields[i]) do
-- this will bet set to an empty value not nil confusingly
if row[field] == nil or row[field] == '' then
if row_info.options[j].optional ~= true then
display = false
break
else
row[field] = nil
end
end
end
if display then
row_info.display(tpl_args, tr, row, display_fields[i])
else
tr:node(m_util.html.table_cell('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:node(m_util.html.table_cell('na'))
end
end
end
for _, key in ipairs(mod_table.weights) do
if tpl_args[key] then
local weight_out = {}
local fields = {
tag = string.format('mod_%s.tag', key),
value = string.format('mod_%s.value', key),
}
for _, wrow in ipairs(weights[key][row['mods._pageID']]) do
if wrow[fields.tag] and wrow[fields.value] then
weight_out[#weight_out+1] = string.format(
'%s %s',
wrow[fields.tag],
wrow[fields.value]
)
end
end
if #weight_out > 0 then
tr
:tag('td')
:wikitext(table.concat(weight_out, '<br>'))
:done()
else
tr:node(m_util.html.table_cell('na'))
end
end
end
for _, field in ipairs(tpl_args._extra_fields) do
if row[field] then
tr
:tag('td')
:wikitext(row[field])
else
tr:node(m_util.html.table_cell('na'))
end
end
end
return tostring(tbl)
end
-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------
local p = {}
--
-- Template:Modifier table
--
p.mod_table = m_util.misc.invoker_factory(_mod_table, {
parentFirst = true,
})
--
-- Debug
--
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