Module:Modifier table: Difference between revisions
Jump to navigation
Jump to search
>Illviljan (Looking at poe.trade shows that attack speed from explicit and implicit can spawn together on 1h axes, separated the mod group counter into explicit and implicit as well.) |
No edit summary |
||
(48 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,675: | 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