Module:Modifier table: Difference between revisions
Jump to navigation
Jump to search
>Illviljan (Drop down mod lists: Separated the display part so it's possible to match mod groups in other sections, do implicit mods follow the mod group rules too? Changed color on mod groups and same mod groups in different sections are now linked to each other.) |
Mefisto1029 (talk | contribs) (no labs) |
||
| (53 intermediate revisions by 8 users not shown) | |||
| Line 1: | Line 1: | ||
-- | ------------------------------------------------------------------------------- | ||
Module responsible for displaying modifiers in various ways. | -- | ||
-- Module:Modifier table | |||
-- | |||
-- Module responsible for displaying modifiers in various ways. Implements | |||
-- Template:Modifier table and Template:Item modifiers | |||
------------------------------------------------------------------------------- | |||
require('strict') | |||
local m_util = require('Module:Util') | |||
-- Should we use the sandbox version of our submodules? | |||
local use_sandbox = m_util.misc.maybe_sandbox('Modifier table') | |||
local | local m_cargo = use_sandbox and require('Module:Cargo/sandbox') or require('Module:Cargo') | ||
local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or mw.loadData('Module:Game') | |||
local m_game = | |||
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:Modifier table/config/sandbox') or mw.loadData('Module:Modifier table/config') | |||
local | local i18n = cfg.i18n | ||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
-- | -- Helper functions | ||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
local | 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=tag', | |||
table_name .. '.value=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 ', ' | |||
local | 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 | |||
end | 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:addClass('tc -' .. args.color) | |||
end | |||
end | |||
end | end | ||
end | end | ||
function h. | function h.tbl.display.factory.weights(args) | ||
return function(tpl_args, tr, data, fields) | |||
local weights = {} | |||
for _, v in ipairs(tpl_args[string.format('_%s_data', args.key)][data['mods._pageID']]) do | |||
if v.tag and v.value then | |||
table.insert(weights, string.format( | |||
'%s %s', | |||
v.tag, | |||
v.value | |||
)) | |||
end | |||
end | |||
if #weights == 0 then | |||
tr | |||
:node(m_util.html.table_cell('na')) | |||
else | |||
tr | |||
:tag('td') | |||
end | :wikitext(table.concat(weights, '<br>')) | ||
end | end | ||
end | end | ||
end | end | ||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
-- | -- Additional configuration | ||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
| Line 195: | Line 131: | ||
}, | }, | ||
}, | }, | ||
display = function(tpl_args | display = function(tpl_args, tr, data, fields) | ||
local name | local name | ||
if data['mods.name'] then | if data['mods.name'] then | ||
| Line 202: | Line 138: | ||
name = data['mods.id'] | name = data['mods.id'] | ||
end | end | ||
tr | tr | ||
:tag('td') | :tag('td') | ||
| Line 213: | Line 150: | ||
header = i18n.mod_table.domain, | header = i18n.mod_table.domain, | ||
fields = {'mods.domain'}, | fields = {'mods.domain'}, | ||
display = function(tpl_args | display = function(tpl_args, tr, data, fields) | ||
local | 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, | end, | ||
order = 2000, | order = 2000, | ||
| Line 227: | Line 166: | ||
header = i18n.mod_table.generation_type, | header = i18n.mod_table.generation_type, | ||
fields = {'mods.generation_type'}, | fields = {'mods.generation_type'}, | ||
display = function(tpl_args | display = function(tpl_args, tr, data, fields) | ||
local | 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, | end, | ||
order = 2001, | order = 2001, | ||
| Line 239: | Line 180: | ||
{ | { | ||
arg = {'group', 'mod_group'}, | arg = {'group', 'mod_group'}, | ||
header = i18n.mod_table. | header = i18n.mod_table.mod_groups, | ||
fields = {'mods. | fields = {'mods.mod_groups'}, | ||
display = function(tpl_args | 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, | end, | ||
order = | order = 2010, | ||
sort_type = 'text', | sort_type = 'text', | ||
}, | }, | ||
| Line 253: | Line 194: | ||
header = i18n.mod_table.mod_type, | header = i18n.mod_table.mod_type, | ||
fields = {'mods.mod_type'}, | fields = {'mods.mod_type'}, | ||
display = | display = h.tbl.display.factory.value{}, | ||
order = 2011, | |||
order = | |||
sort_type = 'text', | sort_type = 'text', | ||
}, | }, | ||
| Line 265: | Line 202: | ||
header = i18n.mod_table.required_level, | header = i18n.mod_table.required_level, | ||
fields = {'mods.required_level'}, | fields = {'mods.required_level'}, | ||
display = | display = h.tbl.display.factory.value{}, | ||
order = 2012, | |||
order = | |||
}, | }, | ||
{ | { | ||
| Line 297: | Line 209: | ||
header = i18n.mod_table.stat_text, | header = i18n.mod_table.stat_text, | ||
fields = {'mods.stat_text'}, | fields = {'mods.stat_text'}, | ||
display = function(tpl_args | display = function(tpl_args, tr, data, fields) | ||
local | local value | ||
-- map display type shows this in another column, remove this text to avoid clogging up the list | -- map display type shows this in another column, remove this text to avoid clogging up the list | ||
if tpl_args.type == 'map' then | if tpl_args.type == 'map' then | ||
local texts = m_util.string.split(data['mods.stat_text'], '<br>') | local texts = m_util.string.split(data['mods.stat_text'], '<br>') | ||
local out = {} | local out = {} | ||
local valid | local valid | ||
for _, v in ipairs(texts) do | for _, v in ipairs(texts) do | ||
| Line 313: | Line 225: | ||
end | end | ||
end | end | ||
if valid then | if valid then | ||
table.insert(out, v) | table.insert(out, v) | ||
end | end | ||
end | end | ||
value = table.concat(out, '<br>') | |||
else | else | ||
value = data['mods.stat_text'] | |||
end | end | ||
data['mods.stat_text'] = value | |||
h.tbl.display.factory.value{color='mod'}(tpl_args, tr, data, fields) | |||
end, | end, | ||
order = 3000, | order = 3000, | ||
sort_type = 'text', | sort_type = 'text', | ||
}, | }, | ||
{ | { | ||
arg = 'buff', | arg = 'buff', | ||
header = i18n.mod_table.buff, | header = i18n.mod_table.buff, | ||
fields = {'mods.granted_buff_id', 'mods.granted_buff_value'}, | fields = {'mods.granted_buff_id', 'mods.granted_buff_value'}, | ||
display = | display = h.tbl.display.factory.value{delimiter=' '}, | ||
order = 4000, | order = 4000, | ||
sort_type = 'text', | sort_type = 'text', | ||
| Line 348: | Line 253: | ||
header = i18n.mod_table.granted_skill, | header = i18n.mod_table.granted_skill, | ||
fields = {'mods.granted_skill'}, | fields = {'mods.granted_skill'}, | ||
display = | display = h.tbl.display.factory.value{}, | ||
order = 4001, | order = 4001, | ||
sort_type = 'text', | sort_type = 'text', | ||
| Line 360: | Line 261: | ||
header = i18n.mod_table.tags, | header = i18n.mod_table.tags, | ||
fields = {'mods.tags'}, | fields = {'mods.tags'}, | ||
display = function(tpl_args | 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, | end, | ||
order = 5000, | order = 5000, | ||
sort_type = 'text', | sort_type = 'text', | ||
}, | }, | ||
{ | |||
arg = {'spawn_weights'}, | |||
header = i18n.mod_table.spawn_weights, | |||
fields = {}, | |||
display = h.tbl.display.factory.weights{key='spawn_weights'}, | |||
order = 6000, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = {'generation_weights'}, | |||
header = i18n.mod_table.generation_weights, | |||
fields = {}, | |||
display = h.tbl.display.factory.weights{key='generation_weights'}, | |||
order = 6001, | |||
sort_type = 'text', | |||
}, | |||
--[[{ | |||
arg = {'game_mode'}, | |||
header = i18n.mod_table.game_modes, | |||
fields = {'mods.game_mode'}, | |||
display = function (tpl_args, tr, data, fields) | |||
local key = 'mods.game_mode' | |||
local value = data[key] | |||
if value ~= nil then | |||
local modes = {} | |||
value = m_util.cast.number(value) | |||
for k, m in ipairs(m_game.modes) do | |||
if value == 0 or value == k then | |||
table.insert(modes, m.short_upper) | |||
end | |||
end | |||
data[key] = table.concat(modes, ', ') | |||
end | |||
h.tbl.display.factory.value{}(tpl_args, tr, data, fields) | |||
end, | |||
order = 6010, | |||
sort_type = 'text', | |||
},--]] | |||
} | } | ||
mod_table.stat_ids = { | mod_table.stat_ids = { | ||
| Line 385: | Line 324: | ||
mod_table.weights = {'spawn_weights', 'generation_weights'} | mod_table.weights = {'spawn_weights', 'generation_weights'} | ||
function | -- ---------------------------------------------------------------------------- | ||
-- Main functions | |||
-- ---------------------------------------------------------------------------- | |||
local function _mod_table(tpl_args) | |||
--[[ | --[[ | ||
Creates a generic table for modifiers. | Creates a generic table for modifiers. | ||
Examples | Examples | ||
-------- | -------- | ||
= p.mod_table{ | = p.mod_table{ | ||
q_tables=' | q_tables='mod_spawn_weights', | ||
q_join='mods._pageID= | q_join='mods._pageID=mod_spawn_weights._pageID', | ||
q_where='mods.generation_type = 10 AND | 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_orderBy='mods.id, mods.required_level', | ||
q_limit=100, | q_limit=100, | ||
| Line 400: | Line 343: | ||
enchantment=1, | enchantment=1, | ||
} | } | ||
]] | ]] | ||
-- default to enabled | -- default to enabled | ||
tpl_args.name = tpl_args.name or true | tpl_args.name = tpl_args.name or true | ||
for _, key in ipairs(mod_table.weights) do | for _, key in ipairs(mod_table.weights) do | ||
tpl_args[key] = m_util.cast.boolean(tpl_args[key]) | tpl_args[key] = m_util.cast.boolean(tpl_args[key]) | ||
end | end | ||
if string.find(tpl_args.q_where, '%[%[') ~= nil then | if string.find(tpl_args.q_where, '%[%[') ~= nil then | ||
error('SMW leftover in where clause') | error('SMW leftover in where clause') | ||
end | end | ||
local row_infos = {} | local row_infos = {} | ||
for _, row_info in ipairs(mod_table.data) do | for _, row_info in ipairs(mod_table.data) do | ||
| Line 427: | Line 363: | ||
elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then | elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then | ||
enabled = true | enabled = true | ||
elseif type(row_info.arg) == 'table' then | elseif type(row_info.arg) == 'table' then | ||
for _, argument in ipairs(row_info.arg) do | for _, argument in ipairs(row_info.arg) do | ||
if m_util.cast.boolean(tpl_args[argument]) then | if m_util.cast.boolean(tpl_args[argument]) then | ||
| Line 435: | Line 371: | ||
end | end | ||
end | end | ||
if enabled then | if enabled then | ||
row_info.options = row_info.options or {} | row_info.options = row_info.options or {} | ||
| Line 441: | Line 377: | ||
end | end | ||
end | end | ||
-- sort the rows | -- sort the rows | ||
table.sort(row_infos, function (a, b) | table.sort(row_infos, function (a, b) | ||
return (a.order or 0) < (b.order or 0) | return (a.order or 0) < (b.order or 0) | ||
end) | end) | ||
-- Set tables | -- Set required and extra tables: | ||
local tables = 'mods' | local tables = {'mods'} | ||
for _, v in ipairs(m_util.string.split_args(tpl_args.q_tables, {',%s*'})) do | |||
tables = | tables[#tables+1] = v | ||
end | end | ||
-- Set required and extra fields: | |||
-- Set required fields | |||
local fields = { | local fields = { | ||
'mods._pageID', | 'mods._pageID', | ||
} | } | ||
for _, | for _, row_info in ipairs(row_infos) do | ||
if type( | if type(row_info.fields) == 'function' then | ||
row_info.fields = row_info.fields() | |||
end | end | ||
for index, field in ipairs( | for index, field in ipairs(row_info.fields) do | ||
row_info.options[index] = row_info.options[index] or {} | |||
fields[#fields+1] = field | fields[#fields+1] = field | ||
end | end | ||
end | end | ||
tpl_args._extra_fields = m_util.string.split_args(tpl_args.q_fields, {',%s*'}) | |||
-- Parse query arguments | for _, v in ipairs(tpl_args._extra_fields) do | ||
fields[#fields+1] = v | |||
end | |||
-- Parse query arguments: | |||
local query = { | local query = { | ||
-- Workaround: fix duplicates | -- Workaround: fix duplicates | ||
groupBy='mods._pageID', | groupBy='mods._pageID', | ||
} | } | ||
for key, value in pairs(tpl_args) do | for key, value in pairs(tpl_args) do | ||
if string.sub(key, 0, 2) == 'q_' then | if string.sub(key, 0, 2) == 'q_' then | ||
query[string.sub(key, 3)] = value | query[string.sub(key, 3)] = value | ||
end | end | ||
end | end | ||
local results = m_cargo.query(tables, fields, query) | |||
local results = | |||
if #results == 0 then | if #results == 0 then | ||
if tpl_args.default ~= nil then | if tpl_args.default ~= nil then | ||
| Line 498: | Line 427: | ||
end | end | ||
end | end | ||
-- this might be needed in other queries, currently not checking if it's actually needed | -- this might be needed in other queries, currently not checking if | ||
-- it's actually needed because performance impact should be neglible | |||
local page_ids = {} | local page_ids = {} | ||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
page_ids[#page_ids+1] = string.format('mods._pageID="%s"', row['mods._pageID']) | page_ids[#page_ids+1] = string.format( | ||
'mods._pageID="%s"', | |||
row['mods._pageID'] | |||
) | |||
end | end | ||
page_ids = table.concat(page_ids, ' OR ') | page_ids = table.concat(page_ids, ' OR ') | ||
for _, key in ipairs(mod_table.weights) do | for _, key in ipairs(mod_table.weights) do | ||
if tpl_args[key] then | if tpl_args[key] then | ||
weights[key] = h.query_weights(key, page_ids) | -- Store weights data in tpl_args to be accessed later | ||
tpl_args[string.format('_%s_data', key)] = h.query_weights('mod_' .. key, page_ids) | |||
end | end | ||
end | end | ||
local stats | local stats | ||
if tpl_args.type == 'map' then | if tpl_args.type == 'map' then | ||
| Line 520: | Line 452: | ||
query_stat_ids[#query_stat_ids+1] = string.format('mod_stats.id="%s"', k) | query_stat_ids[#query_stat_ids+1] = string.format('mod_stats.id="%s"', k) | ||
end | 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 | -- In addition map stats to stat <-> min/max pairs | ||
for page_id, rows in pairs(stats) do | for page_id, rows in pairs(stats) do | ||
local stat_id_map = {} | local stat_id_map = {} | ||
for _, row in ipairs(rows) do | 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'])} | stat_id_map[row['mod_stats.id']] = { | ||
min=tonumber(row['mod_stats.min']), | |||
max=tonumber(row['mod_stats.max']) | |||
} | |||
end | end | ||
stats[page_id] = stat_id_map | stats[page_id] = stat_id_map | ||
end | end | ||
end | end | ||
-- | -- | ||
-- Display | -- Display | ||
-- | -- | ||
-- Preformance optimization | -- 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:addClass('wikitable sortable modifier-table') | |||
if m_util.cast.boolean(tpl_args.responsive) then | |||
tbl:addClass('responsive-table') | |||
end | end | ||
-- Header | -- Header | ||
local tr = tbl:tag('tr') | local tr = tbl:tag('tr') | ||
for | local display_fields = {} | ||
for i, row_info in ipairs(row_infos) do | |||
display_fields[i] = display_fields[i] or {} | |||
for j, field in ipairs(row_info.fields) do | |||
-- Aliased name is used as keys in the results: | |||
local name = m_util.string.split(field, '%s*=%s*') | |||
name = name[2] or name[1] | |||
display_fields[i][j] = name | |||
end | |||
tr | tr | ||
:tag('th') | :tag('th') | ||
| Line 574: | Line 523: | ||
:done() | :done() | ||
end | end | ||
if tpl_args.type == 'map' then | if tpl_args.type == 'map' then | ||
for stat_id, data in pairs(mod_table.stat_ids) do | for stat_id, data in pairs(mod_table.stat_ids) do | ||
| Line 584: | Line 533: | ||
end | end | ||
end | end | ||
for _, field in ipairs(tpl_args._extra_fields) do | for _, field in ipairs(tpl_args._extra_fields) do | ||
tr | tr | ||
| Line 598: | Line 539: | ||
:wikitext(field) | :wikitext(field) | ||
end | end | ||
-- Body | -- Body | ||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
tr = tbl:tag('tr') | tr = tbl:tag('tr') | ||
for | for i, row_info in ipairs(row_infos) do | ||
-- this has been cast from a function in an earlier step | -- this has been cast from a function in an earlier step | ||
local display = true | local display = true | ||
for | for j, field in ipairs(display_fields[i]) do | ||
-- this will bet set to an empty value not nil confusingly | -- this will bet set to an empty value not nil confusingly | ||
if row[field] == '' then | if row[field] == nil or row[field] == '' then | ||
if | if row_info.options[j].optional ~= true then | ||
display = false | display = false | ||
break | break | ||
| Line 619: | Line 560: | ||
end | end | ||
if display then | if display then | ||
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 | ||
if tpl_args.type == 'map' then | if tpl_args.type == 'map' then | ||
for stat_id, data in pairs(mod_table.stat_ids) do | for stat_id, data in pairs(mod_table.stat_ids) do | ||
| Line 642: | Line 583: | ||
:done() | :done() | ||
else | else | ||
tr: | tr:node(m_util.html.table_cell('na')) | ||
end | end | ||
end | end | ||
end | end | ||
for _, field in ipairs(tpl_args._extra_fields) do | for _, field in ipairs(tpl_args._extra_fields) do | ||
if row[field] then | if row[field] then | ||
| Line 670: | Line 594: | ||
:wikitext(row[field]) | :wikitext(row[field]) | ||
else | else | ||
tr: | tr:node(m_util.html.table_cell('na')) | ||
end | end | ||
end | end | ||
end | end | ||
return tostring(tbl) | return tostring(tbl) | ||
end | end | ||
-- ---------------------------------------------------------------------------- | |||
-- Exported functions | |||
-- ---------------------------------------------------------------------------- | |||
local p = {} | |||
-- | |||
-- Template:Modifier table | |||
-- | |||
p.mod_table = m_util.misc.invoker_factory(_mod_table, { | |||
parentFirst = true, | |||
}) | |||
-- | |||
-- Debug | |||
-- | |||
-- Debug | |||
-- | |||
p.debug = {} | p.debug = {} | ||
function p.debug.tbl_data(tbl) | function p.debug.tbl_data(tbl) | ||
keys = {} | keys = {} | ||
for _, data in ipairs(mod_table.data) do | for _, data in ipairs(mod_table.data) do | ||
if type(data.arg) == 'string' then | if type(data.arg) == 'string' then | ||
keys[data.arg] = 1 | keys[data.arg] = 1 | ||
elseif type(data.arg) == 'table' then | elseif type(data.arg) == 'table' then | ||
| Line 1,671: | Line 630: | ||
end | end | ||
end | end | ||
for _, key in ipairs(mod_table.weights) do | for _, key in ipairs(mod_table.weights) do | ||
keys[key] = 1 | keys[key] = 1 | ||
end | end | ||
local out = {} | local out = {} | ||
for key, _ in pairs(keys) do | for key, _ in pairs(keys) do | ||
out[#out+1] = string.format("['%s'] = '1'", key) | out[#out+1] = string.format("['%s'] = '1'", key) | ||
end | end | ||
return table.concat(out, ', ') | return table.concat(out, ', ') | ||
end | end | ||
return p | return p | ||
Latest revision as of 17:16, 7 December 2025
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('strict')
local m_util = require('Module:Util')
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Modifier table')
local m_cargo = use_sandbox and require('Module:Cargo/sandbox') or require('Module:Cargo')
local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or 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=tag',
table_name .. '.value=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:addClass('tc -' .. args.color)
end
end
end
end
function h.tbl.display.factory.weights(args)
return function(tpl_args, tr, data, fields)
local weights = {}
for _, v in ipairs(tpl_args[string.format('_%s_data', args.key)][data['mods._pageID']]) do
if v.tag and v.value then
table.insert(weights, string.format(
'%s %s',
v.tag,
v.value
))
end
end
if #weights == 0 then
tr
:node(m_util.html.table_cell('na'))
else
tr
:tag('td')
:wikitext(table.concat(weights, '<br>'))
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 = 2010,
sort_type = 'text',
},
{
arg = {'mod_type'},
header = i18n.mod_table.mod_type,
fields = {'mods.mod_type'},
display = h.tbl.display.factory.value{},
order = 2011,
sort_type = 'text',
},
{
arg = {'level', 'required_level'},
header = i18n.mod_table.required_level,
fields = {'mods.required_level'},
display = h.tbl.display.factory.value{},
order = 2012,
},
{
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',
},
{
arg = {'spawn_weights'},
header = i18n.mod_table.spawn_weights,
fields = {},
display = h.tbl.display.factory.weights{key='spawn_weights'},
order = 6000,
sort_type = 'text',
},
{
arg = {'generation_weights'},
header = i18n.mod_table.generation_weights,
fields = {},
display = h.tbl.display.factory.weights{key='generation_weights'},
order = 6001,
sort_type = 'text',
},
--[[{
arg = {'game_mode'},
header = i18n.mod_table.game_modes,
fields = {'mods.game_mode'},
display = function (tpl_args, tr, data, fields)
local key = 'mods.game_mode'
local value = data[key]
if value ~= nil then
local modes = {}
value = m_util.cast.number(value)
for k, m in ipairs(m_game.modes) do
if value == 0 or value == k then
table.insert(modes, m.short_upper)
end
end
data[key] = table.concat(modes, ', ')
end
h.tbl.display.factory.value{}(tpl_args, tr, data, fields)
end,
order = 6010,
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 ')
for _, key in ipairs(mod_table.weights) do
if tpl_args[key] then
-- Store weights data in tpl_args to be accessed later
tpl_args[string.format('_%s_data', 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:addClass('wikitable sortable modifier-table')
if m_util.cast.boolean(tpl_args.responsive) then
tbl:addClass('responsive-table')
end
-- Header
local tr = tbl:tag('tr')
local display_fields = {}
for i, row_info in ipairs(row_infos) do
display_fields[i] = display_fields[i] or {}
for j, field in ipairs(row_info.fields) do
-- Aliased name is used as keys in the results:
local name = m_util.string.split(field, '%s*=%s*')
name = name[2] or name[1]
display_fields[i][j] = name
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 _, 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 _, 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