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.) |
No edit summary |
||
(50 intermediate revisions by 7 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('Module:No globals') | |||
local m_util = require('Module:Util') | local m_util = require('Module:Util') | ||
local m_cargo = require('Module:Cargo') | local m_cargo = require('Module:Cargo') | ||
local | -- Should we use the sandbox version of our submodules? | ||
local use_sandbox = m_util.misc.maybe_sandbox('Modifier table') | |||
local | 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 = {} | local h = {} | ||
function 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 | 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 ', ' | |||
function | 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 | 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 | |||
end | end | ||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
-- | -- Additional configuration | ||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
Line 195: | Line 108: | ||
}, | }, | ||
}, | }, | ||
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 115: | ||
name = data['mods.id'] | name = data['mods.id'] | ||
end | end | ||
tr | tr | ||
:tag('td') | :tag('td') | ||
Line 213: | Line 127: | ||
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 143: | ||
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 157: | ||
{ | { | ||
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 = 2002, | order = 2002, | ||
Line 253: | Line 171: | ||
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 = 2003, | order = 2003, | ||
sort_type = 'text', | sort_type = 'text', | ||
Line 265: | Line 179: | ||
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 = 2004, | order = 2004, | ||
}, | }, | ||
Line 276: | Line 186: | ||
header = i18n.mod_table.labyrinth, | header = i18n.mod_table.labyrinth, | ||
fields = {string.format([[ | fields = {string.format([[ | ||
CONCAT( | 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" | |||
)=labyrinth_text | END | ||
)=labyrinth_text]], | |||
display = | 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, | order = 2005, | ||
sort_type = 'text', | sort_type = 'text', | ||
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, | ||
Line 385: | Line 286: | ||
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 305: | ||
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 325: | ||
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 333: | ||
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 339: | ||
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 389: | ||
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 ') | ||
local weights = {} | local weights = {} | ||
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) | weights[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 414: | ||
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 | end | ||
local tbl = mw.html.create('table') | local tbl = mw.html.create('table') | ||
tbl:attr('class', 'wikitable sortable modifier-table') | tbl:attr('class', 'wikitable sortable modifier-table') | ||
-- 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 | |||
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 | tr | ||
:tag('th') | :tag('th') | ||
Line 574: | Line 487: | ||
: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 497: | ||
end | end | ||
end | end | ||
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 | ||
Line 592: | Line 505: | ||
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 511: | ||
: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 532: | ||
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 555: | ||
:done() | :done() | ||
else | else | ||
tr: | tr:node(m_util.html.table_cell('na')) | ||
end | end | ||
end | end | ||
end | end | ||
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 | ||
local weight_out = {} | 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 | for _, wrow in ipairs(weights[key][row['mods._pageID']]) do | ||
weight_out[#weight_out+1] = string.format('%s %s', wrow[ | 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 | end | ||
if #weight_out > 0 then | if #weight_out > 0 then | ||
tr | tr | ||
:tag('td') | |||
:wikitext(table.concat(weight_out, '<br>')) | |||
: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 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