Path of Exile Wiki

Please consider helping keep the wiki up to date. Check the to-do list of updates needed for version 3.14.0.

Game data exports will becoming later as the technical changes in addition to regular changes take some more time.

READ MORE

Path of Exile Wiki
m (domains have changed)
m (Call it veiled mods instead.)
(34 intermediate revisions by 2 users not shown)
Line 8: Line 8:
 
local m_game = require('Module:Game')
 
local m_game = require('Module:Game')
 
local f_item_link = require('Module:Item link').item_link
 
local f_item_link = require('Module:Item link').item_link
  +
local m_cargo = require('Module:Cargo')
   
 
local cargo = mw.ext.cargo
 
local cargo = mw.ext.cargo
Line 33: Line 34:
 
buff = m_util.html.abbr('Buff', 'ID of the buff granted and the values associated'),
 
buff = m_util.html.abbr('Buff', 'ID of the buff granted and the values associated'),
 
granted_skill = m_util.html.abbr('Skill', 'ID of the skill granted'),
 
granted_skill = m_util.html.abbr('Skill', 'ID of the skill granted'),
tags = '[[Tags]]',
+
tags = '[[Modifiers#Tags|Tags]]',
  +
 
 
iiq = m_util.html.abbr('IIQ', 'increased Quantity of Items found in this Area'),
 
iiq = m_util.html.abbr('IIQ', 'increased Quantity of Items found in this Area'),
 
iir = m_util.html.abbr('IIR', 'increased Rarity of Items found in this Area'),
 
iir = m_util.html.abbr('IIR', 'increased Rarity of Items found in this Area'),
 
pack_size = m_util.html.abbr('Pack<br>Size', 'Monster pack size'),
 
pack_size = m_util.html.abbr('Pack<br>Size', 'Monster pack size'),
  +
 
 
spawn_weights = m_util.html.abbr('Spawn Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
 
spawn_weights = m_util.html.abbr('Spawn Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
 
generation_weights = m_util.html.abbr('Generation Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
 
generation_weights = m_util.html.abbr('Generation Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
  +
 
 
normal_labyrinth = 'Normal Labyrinth',
 
normal_labyrinth = 'Normal Labyrinth',
 
cruel_labyrinth = 'Cruel Labyrinth',
 
cruel_labyrinth = 'Cruel Labyrinth',
Line 47: Line 48:
 
eternal_labyrinth = 'Eternal Labyrinth',
 
eternal_labyrinth = 'Eternal Labyrinth',
 
},
 
},
  +
 
 
drop_down_table = {
 
drop_down_table = {
 
collapse_all = 'Collapse all',
 
collapse_all = 'Collapse all',
Line 54: Line 55:
 
prefix = 'Prefix',
 
prefix = 'Prefix',
 
suffix = 'Suffix',
 
suffix = 'Suffix',
corrupted = 'Corrupted',
+
corrupted = 'Corrupted',
 
enchant = 'Enchantment',
 
enchant = 'Enchantment',
 
elder_prefix = 'Elder prefix',
 
elder_prefix = 'Elder prefix',
Line 60: Line 61:
 
shaper_prefix = 'Shaper prefix',
 
shaper_prefix = 'Shaper prefix',
 
shaper_suffix = 'Shaper suffix',
 
shaper_suffix = 'Shaper suffix',
  +
delve_prefix = 'Delve prefix',
  +
delve_suffix = 'Delve suffix',
  +
crusader_prefix = 'Crusader prefix',
  +
crusader_suffix = 'Crusader suffix',
  +
eyrie_prefix = 'Redeemer prefix',
  +
eyrie_suffix = 'Redeemer suffix',
  +
basilisk_prefix = 'Hunter prefix',
  +
basilisk_suffix = 'Hunter suffix',
  +
adjudicator_prefix = 'Warlord prefix',
  +
adjudicator_suffix = 'Warlord suffix',
  +
veiled_prefix = 'Veiled prefix',
  +
veiled_suffix = 'Veiled suffix',
 
mod_group = 'Mod group:',
 
mod_group = 'Mod group:',
  +
back_to_top = 'Back to top',
 
},
 
},
  +
 
 
errors = {
 
errors = {
 
--
 
--
Line 69: Line 83:
 
sell_price_duplicate_name = 'Do not specify a sell price item name multiple times. Adjust the amount instead.',
 
sell_price_duplicate_name = 'Do not specify a sell price item name multiple times. Adjust the amount instead.',
 
sell_price_missing_argument = 'Both %s and %s must be specified',
 
sell_price_missing_argument = 'Both %s and %s must be specified',
  +
 
 
--
 
--
 
-- Modifier link template
 
-- Modifier link template
Line 89: Line 103:
 
function h.header(str)
 
function h.header(str)
 
--[[
 
--[[
This function replaces specific numbers with a generic #.
+
This function replaces specific numbers with a generic #.
 
]]
 
]]
  +
 
 
local s = table.concat(m_util.string.split(str, '%(%d+%.*%d*%-%d+%.*%d*%)'), '#')
 
local s = table.concat(m_util.string.split(str, '%(%d+%.*%d*%-%d+%.*%d*%)'), '#')
 
s = table.concat(m_util.string.split(s, '%d+%.*%d*'), '#')
 
s = table.concat(m_util.string.split(s, '%d+%.*%d*'), '#')
 
s = table.concat(m_util.string.split(s, '<br>'), ', ')
 
s = table.concat(m_util.string.split(s, '<br>'), ', ')
 
s = table.concat(m_util.string.split(s, '<br />'), ' ')
 
s = table.concat(m_util.string.split(s, '<br />'), ' ')
  +
 
 
return s
 
return s
 
end
 
end
   
 
function h.query_weights(table_name, page_ids)
 
function h.query_weights(table_name, page_ids)
  +
return m_cargo.map_results_to_id{
results = cargo.query(
 
  +
results=m_cargo.query(
string.format('mods,%s', table_name),
 
string.format('mods._pageID,%s.tag,%s.weight', table_name, table_name),
+
{'mods', table_name},
{
+
{
where=page_ids,
+
'mods._pageID',
join=string.format('mods._pageID=%s._pageID', table_name),
+
table_name .. '.tag',
orderBy=string.format('mods.id ASC,%s.ordinal ASC', table_name),
+
table_name .. '.weight'
limit=5000,
+
},
}
+
{
  +
where=page_ids,
)
 
  +
join=string.format('mods._pageID=%s._pageID', table_name),
if #results == 5000 then
 
  +
orderBy=string.format('mods.id ASC,%s.ordinal ASC', table_name),
error('Hit maximum cargo results')
 
end
+
}
+
),
return m_util.cargo.map_results_to_id{results=results,field='mods._pageID'}
+
field='mods._pageID',
  +
}
 
end
 
end
   
Line 125: Line 140:
 
for i,v in pairs(results) do
 
for i,v in pairs(results) do
 
str[#str+1] = string.format(
 
str[#str+1] = string.format(
'%s - %s ([[%s|page]])',
+
'%s - %s ([[%s|page]])',
v['mods.id'] or v['mods._pageName'],
+
v['mods.id'] or v['mods._pageName'] or '',
 
string.gsub(
 
string.gsub(
v['mods.stat_text_raw'] or 'N/A',
+
v['mods.stat_text_raw'] or 'N/A',
'<br>',
+
'<br>',
 
', '
 
', '
 
) or '',
 
) or '',
v['mods._pageName']
+
v['mods._pageName'] or ''
 
)
 
)
end
+
end
 
return table.concat(str, '<br>')
 
return table.concat(str, '<br>')
 
end
 
end
   
  +
h.tbl = {}
function h.cargo_query(tpl_args)
 
  +
h.tbl.display = {}
--[[
 
  +
h.tbl.display.factory = {}
Returns a Cargo query of all the results.
 
  +
function h.tbl.display.factory.value(args)
 
  +
-- Format options for each field:
tpl_args to be added to the cargo table needs to prefixed with q_.
 
  +
args.options = args.options or {}
* tpl_args.q_*
 
  +
 
  +
-- Separator between fields:
]]
 
  +
args.delimiter = args.delimiter or ', '
 
  +
-- Parse query arguments
 
  +
return function(tpl_args, frame, tr, data, fields)
local query = {
 
limit = 5000,
+
local values = {}
offset = 0,
+
local fmt_values = {}
  +
}
 
for key, value in pairs(tpl_args) do
+
for index, field in ipairs(fields) do
if string.sub(key, 0, 2) == 'q_' then
+
local value = {
query[string.sub(key, 3)] = 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, nil, value, opts)
  +
end
 
end
 
end
end
 
 
-- Query cargo table. If there are too many results then repeat,
 
-- offset, re-query and add the remaining results:
 
local results = {}
 
repeat
 
local result = cargo.query(
 
query.tables,
 
query.fields,
 
query
 
)
 
query.offset = query.offset + #result
 
   
for _,v in ipairs(result) do
+
if #values == 0 then
results[#results + 1] = v
+
tr
  +
:wikitext(m_util.html.td.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
until #result < query.limit
 
 
return results
 
 
end
 
end
   
Line 184: Line 206:
 
mod_table.data = {
 
mod_table.data = {
 
{
 
{
arg = nil,
+
arg = 'name',
 
header = i18n.mod_table.name,
 
header = i18n.mod_table.name,
 
fields = {'mods._pageName', 'mods.id', 'mods.name'},
 
fields = {'mods._pageName', 'mods.id', 'mods.name'},
Line 192: Line 214:
 
},
 
},
 
},
 
},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
 
local name
 
local name
 
if data['mods.name'] then
 
if data['mods.name'] then
Line 199: Line 221:
 
name = data['mods.id']
 
name = data['mods.id']
 
end
 
end
  +
 
tr
 
tr
 
:tag('td')
 
:tag('td')
Line 210: Line 233:
 
header = i18n.mod_table.domain,
 
header = i18n.mod_table.domain,
 
fields = {'mods.domain'},
 
fields = {'mods.domain'},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
local value = data['mods.domain']
+
local k = 'mods.domain'
tr
+
local i = tonumber(data[k])
:tag('td')
+
data[k] = m_game.constants.mod.domains[i]['short_upper']
:attr('data-sort-value', value)
+
h.tbl.display.factory.value({})(tpl_args, frame, tr, data, fields)
:wikitext(m_game.constants.mod.domains[tonumber(value)]['short_upper'])
 
 
end,
 
end,
 
order = 2000,
 
order = 2000,
Line 224: Line 246:
 
header = i18n.mod_table.generation_type,
 
header = i18n.mod_table.generation_type,
 
fields = {'mods.generation_type'},
 
fields = {'mods.generation_type'},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
local value = data['mods.generation_type']
+
local k = 'mods.generation_type'
tr
+
local i = tonumber(data[k])
:tag('td')
+
data[k] = m_game.constants.mod.generation_types[i]['short_upper']
:attr('data-sort-value', value)
+
h.tbl.display.factory.value{}(tpl_args, frame, tr, data, fields)
:wikitext(m_game.constants.mod.generation_types[tonumber(value)]['short_upper'])
 
 
end,
 
end,
 
order = 2001,
 
order = 2001,
Line 238: Line 259:
 
header = i18n.mod_table.mod_group,
 
header = i18n.mod_table.mod_group,
 
fields = {'mods.mod_group'},
 
fields = {'mods.mod_group'},
display = function(tpl_args, frame, tr, data)
+
display = h.tbl.display.factory.value{},
tr
 
:tag('td')
 
:wikitext(data['mods.mod_group'])
 
end,
 
 
order = 2002,
 
order = 2002,
 
sort_type = 'text',
 
sort_type = 'text',
Line 250: Line 267:
 
header = i18n.mod_table.mod_type,
 
header = i18n.mod_table.mod_type,
 
fields = {'mods.mod_type'},
 
fields = {'mods.mod_type'},
display = function(tpl_args, frame, tr, data)
+
display = h.tbl.display.factory.value{},
tr
 
:tag('td')
 
:wikitext(data['mods.mod_type'])
 
end,
 
 
order = 2003,
 
order = 2003,
 
sort_type = 'text',
 
sort_type = 'text',
Line 262: Line 275:
 
header = i18n.mod_table.required_level,
 
header = i18n.mod_table.required_level,
 
fields = {'mods.required_level'},
 
fields = {'mods.required_level'},
display = function(tpl_args, frame, tr, data)
+
display = h.tbl.display.factory.value{},
tr
 
:tag('td')
 
:wikitext(data['mods.required_level'])
 
end,
 
 
order = 2004,
 
order = 2004,
 
},
 
},
Line 273: Line 282:
 
header = i18n.mod_table.labyrinth,
 
header = i18n.mod_table.labyrinth,
 
fields = {string.format([[
 
fields = {string.format([[
CONCAT(
+
CONCAT(
CASE mods.required_level
+
CASE mods.required_level
WHEN 32 THEN "%s"
+
WHEN 32 THEN "%s"
WHEN 53 THEN "%s"
+
WHEN 53 THEN "%s"
WHEN 66 THEN "%s"
+
WHEN 66 THEN "%s"
WHEN 75 THEN "%s"
+
WHEN 75 THEN "%s"
END
+
END
)=labyrinth_text
+
)=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.normal_labyrinth,
  +
i18n.mod_table.cruel_labyrinth,
display = function(tpl_args, frame, tr, data)
 
tr
+
i18n.mod_table.merciless_labyrinth,
:tag('td')
+
i18n.mod_table.eternal_labyrinth)
  +
},
:wikitext(data['labyrinth_text'])
 
  +
display = h.tbl.display.factory.value{},
end,
 
 
order = 2005,
 
order = 2005,
  +
sort_type = 'text',
 
},
 
},
 
{
 
{
Line 293: Line 303:
 
header = i18n.mod_table.stat_text,
 
header = i18n.mod_table.stat_text,
 
fields = {'mods.stat_text'},
 
fields = {'mods.stat_text'},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
local text
+
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 309: Line 319:
 
end
 
end
 
end
 
end
  +
 
 
if valid then
 
if valid then
 
table.insert(out, v)
 
table.insert(out, v)
 
end
 
end
 
end
 
end
  +
 
text = table.concat(out, '<br>')
+
value = table.concat(out, '<br>')
 
else
 
else
text = data['mods.stat_text']
+
value = data['mods.stat_text']
 
end
 
end
+
data['mods.stat_text'] = value
  +
h.tbl.display.factory.value{color='mod'}(tpl_args, frame, tr, data, fields)
tr
 
:tag('td')
 
:wikitext(text)
 
 
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 = function(tpl_args, frame, tr, data)
+
display = h.tbl.display.factory.value{delimiter=' '},
tr
 
:tag('td')
 
:wikitext(string.format('%s %s', data['mods.granted_buff_id'], data['mods.granted_buff_value']))
 
end,
 
 
order = 4000,
 
order = 4000,
 
sort_type = 'text',
 
sort_type = 'text',
Line 344: Line 347:
 
header = i18n.mod_table.granted_skill,
 
header = i18n.mod_table.granted_skill,
 
fields = {'mods.granted_skill'},
 
fields = {'mods.granted_skill'},
display = function(tpl_args, frame, tr, data)
+
display = h.tbl.display.factory.value{},
tr
 
:tag('td')
 
:wikitext(data['mods.granted_skill'])
 
end,
 
 
order = 4001,
 
order = 4001,
 
sort_type = 'text',
 
sort_type = 'text',
Line 356: Line 355:
 
header = i18n.mod_table.tags,
 
header = i18n.mod_table.tags,
 
fields = {'mods.tags'},
 
fields = {'mods.tags'},
display = function(tpl_args, frame, tr, data)
+
display = function(tpl_args, frame, tr, data, fields)
tr
+
local k = 'mods.tags'
:tag('td')
+
data[k] = table.concat(m_util.string.split(data[k], ',%s*'), ', ')
:wikitext(table.concat(m_util.string.split(data['mods.tags'], ','), ', '))
+
h.tbl.display.factory.value{}(tpl_args, frame, tr, data, fields)
 
end,
 
end,
 
order = 5000,
 
order = 5000,
Line 382: Line 381:
   
 
function p.mod_table(frame)
 
function p.mod_table(frame)
  +
--[[
  +
Creates a generic table for modifiers.
  +
  +
Examples
  +
--------
  +
= p.mod_table{
  +
q_tables='spawn_weights',
  +
q_join='mods._pageID=spawn_weights._pageID',
  +
q_where='mods.generation_type = 10 AND spawn_weights.tag = "boots" AND spawn_weights.weight > 0',
  +
q_orderBy='mods.id, mods.required_level',
  +
q_limit=100,
  +
stat_text=1,
  +
enchantment=1,
  +
}
  +
  +
]]
  +
  +
 
tpl_args = getArgs(frame, {
 
tpl_args = getArgs(frame, {
 
parentFirst = true
 
parentFirst = true
 
})
 
})
 
frame = m_util.misc.get_frame(frame)
 
frame = m_util.misc.get_frame(frame)
  +
 
  +
-- default to enabled
  +
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 402: Line 422:
 
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 410: Line 430:
 
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 416: Line 436:
 
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'}
if tpl_args.q_tables then
+
for _, v in ipairs(m_util.string.split_args(tpl_args.q_tables, {',%s*'})) do
tables = tables .. ',' .. tpl_args.q_tables
+
tables[#tables+1] = v
 
end
 
end
  +
 
  +
-- Set required and extra fields:
 
-- Set required fields
 
 
local fields = {
 
local fields = {
 
'mods._pageID',
 
'mods._pageID',
 
}
 
}
for _, rowinfo in ipairs(row_infos) do
+
for _, row_info in ipairs(row_infos) do
if type(rowinfo.fields) == 'function' then
+
if type(row_info.fields) == 'function' then
rowinfo.fields = rowinfo.fields()
+
row_info.fields = row_info.fields()
 
end
 
end
for index, field in ipairs(rowinfo.fields) do
+
for index, field in ipairs(row_info.fields) do
rowinfo.options[index] = rowinfo.options[index] or {}
+
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*'})
 
  +
for _, v in ipairs(tpl_args._extra_fields) do
-- Parse query arguments
 
  +
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)
 
  +
fields = table.concat(fields, ',')
 
if tpl_args.q_fields then
 
fields = fields .. ' ,' .. tpl_args.q_fields
 
end
 
 
local results = cargo.query(
 
tables,
 
fields,
 
query
 
)
 
 
 
if #results == 0 then
 
if #results == 0 then
 
if tpl_args.default ~= nil then
 
if tpl_args.default ~= nil then
Line 473: Line 486:
 
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
-- because performance impact should be neglible
+
-- 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
Line 488: Line 504:
 
end
 
end
 
end
 
end
  +
 
 
local stats
 
local stats
 
if tpl_args.type == 'map' then
 
if tpl_args.type == 'map' then
Line 495: Line 511:
 
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
  +
 
local stat_results = cargo.query(
+
stats = m_cargo.map_results_to_id{
'mods,mod_stats',
+
results=m_cargo.query(
'mods._pageID,mod_stats.id,mod_stats.min,mod_stats.max',
+
{'mods', 'mod_stats'},
{
+
{
where=string.format('(%s) AND (%s)', page_ids, table.concat(query_stat_ids, ' OR ')),
+
'mods._pageID',
join='mods._pageID=mod_stats._pageID',
+
'mod_stats.id',
orderBy='mods.id ASC',
+
'mod_stats.min',
limit=5000,
+
'mod_stats.max',
}
+
},
)
+
{
  +
where=string.format(
if #stat_results == 5000 then
 
error('Hit maximum cargo results')
+
'(%s) AND (%s)',
end
+
page_ids,
  +
table.concat(query_stat_ids, ' OR ')
 
  +
),
stats = m_util.cargo.map_results_to_id{results=stat_results, field='mods._pageID'}
 
  +
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
if tpl_args.q_fields then
+
for index, field in ipairs(tpl_args._extra_fields) do
tpl_args._extra_fields = m_util.string.split(tpl_args.q_fields, ',')
+
field = m_util.string.split(field, '%s*=%s*')
  +
-- field[2] will be nil if there is no alias
for index, field in ipairs(tpl_args._extra_fields) do
 
field = m_util.string.split(field, '=')
+
tpl_args._extra_fields[index] = field[2] or field[1]
-- field[2] will be nil if there is no alias
 
tpl_args._extra_fields[index] = field[2] or field[1]
 
end
 
else
 
tpl_args._extra_fields = {}
 
 
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')
  +
local display_fields = {}
for _, row_info in ipairs(row_infos) do
 
  +
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 549: Line 584:
 
: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 559: Line 594:
 
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 567: Line 602:
 
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 573: Line 608:
 
: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 _, rowinfo in ipairs(row_infos) do
+
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 index, field in ipairs(rowinfo.fields) do
+
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 rowinfo.options[index].optional ~= true then
+
if row_info.options[j].optional ~= true then
 
display = false
 
display = false
 
break
 
break
Line 594: Line 629:
 
end
 
end
 
if display then
 
if display then
rowinfo.display(tpl_args, frame, tr, row, rowinfo.fields)
+
row_info.display(tpl_args, frame, tr, row, display_fields[i])
 
else
 
else
 
tr:wikitext(m_util.html.td.na())
 
tr:wikitext(m_util.html.td.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 621: Line 656:
 
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 = {}
 
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[key .. '.tag'], wrow[key .. '.weight'])
+
if wrow[key .. '.tag'] and wrow[key .. '.weight'] then
  +
weight_out[#weight_out+1] = string.format(
  +
'%s %s',
  +
wrow[key .. '.tag'],
  +
wrow[key .. '.weight']
  +
)
  +
end
 
end
 
end
  +
 
if #weight_out > 0 then
 
if #weight_out > 0 then
 
tr
 
tr
:tag('td')
+
:tag('td')
:wikitext(table.concat(weight_out, '<br>'))
+
:wikitext(table.concat(weight_out, '<br>'))
:done()
+
:done()
else
+
else
 
tr:wikitext(m_util.html.td.na())
 
tr:wikitext(m_util.html.td.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 649: Line 691:
 
end
 
end
 
end
 
end
  +
 
 
return tostring(tbl)
 
return tostring(tbl)
 
end
 
end
Line 659: Line 701:
 
-- Modifier link
 
-- Modifier link
 
-- ---------------------------------------------------------------------
 
-- ---------------------------------------------------------------------
  +
   
 
function p.modifier_link(frame)
 
function p.modifier_link(frame)
 
--[[
 
--[[
 
Finds and links to a modifier in formatted form.
 
Finds and links to a modifier in formatted form.
  +
 
Examples:
+
To do list
  +
----------
  +
* Standardize hoverbox so it can be used in multiple places easily
  +
and make it easy to add rows of data.
  +
  +
Examples
  +
--------
 
= p.modifier_link{"Tyrannical"}
 
= p.modifier_link{"Tyrannical"}
 
= p.modifier_link{"Flaring"}
 
= p.modifier_link{"Flaring"}
Line 670: Line 719:
 
= p.modifier_link{"StrDexMaster%"}
 
= p.modifier_link{"StrDexMaster%"}
 
= p.modifier_link{"LocalIncreasedPhysicalDamagePercentAndAccuracyRating8", display='max', statid='local_physical_damage_+%'}
 
= p.modifier_link{"LocalIncreasedPhysicalDamagePercentAndAccuracyRating8", display='max', statid='local_physical_damage_+%'}
  +
 
 
]]
 
]]
   
Line 676: Line 725:
 
local tpl_args = getArgs(frame, {parentFirst = true})
 
local tpl_args = getArgs(frame, {parentFirst = true})
 
local frame = m_util.misc.get_frame(frame)
 
local frame = m_util.misc.get_frame(frame)
  +
 
 
-- Aliases:
 
-- Aliases:
 
tpl_args.modid = tpl_args.modid or tpl_args.id or tpl_args[1] or ''
 
tpl_args.modid = tpl_args.modid or tpl_args.id or tpl_args[1] or ''
  +
 
-- Define query arguments:
+
-- Query cargo rows:
  +
tpl_args.results = m_cargo.map_results_to_id{
local tables = {'mods', 'mod_stats', 'spawn_weights'}
 
  +
results=m_cargo.query(
local fields = {'mods.name', 'mods.stat_text', 'mods.stat_text_raw', 'mods.generation_type', 'mods._pageName', 'mod_stats.max', 'mod_stats.min', 'spawn_weights.tag', 'spawn_weights.weight', 'mods.id', 'mod_stats.id'}
 
  +
{'mods', 'mod_stats', 'spawn_weights'},
local query = {
 
  +
{
join = 'mods._pageName=mod_stats._pageName, mods._pageName=spawn_weights._pageName',
 
where = string.format(
+
'mods.name',
'(mods.name LIKE "%s" or mods.id LIKE "%s" or mods.stat_text LIKE "%s" or mods.stat_text_raw LIKE "%s") AND mod_stats.id LIKE "%%%s%%"',
+
'mods.stat_text',
tpl_args.modid,
+
'mods.stat_text_raw',
tpl_args.modid,
+
'mods.generation_type',
tpl_args.modid,
+
'mods.tags',
tpl_args.modid,
+
'mods._pageName',
tpl_args.statid or '%'
+
'mod_stats.max',
  +
'mod_stats.min',
  +
'spawn_weights.tag',
  +
'spawn_weights.weight',
  +
'mods.id',
  +
'mod_stats.id'
  +
},
  +
{
  +
join = [[
  +
mods._pageName=mod_stats._pageName,
  +
mods._pageName=spawn_weights._pageName
  +
]],
  +
where = string.format(
  +
[[
  +
(
  +
mods.name LIKE "%s" or
  +
mods.id LIKE "%s" or
  +
mods.stat_text LIKE "%s" or
  +
mods.stat_text_raw LIKE "%s"
  +
)
  +
AND mod_stats.id LIKE "%%%s%%"
  +
]],
  +
tpl_args.modid,
  +
tpl_args.modid,
  +
tpl_args.modid,
  +
tpl_args.modid,
  +
tpl_args.statid or '%'
  +
),
  +
-- groupBy = 'mods._pageID, mod_stats.id, spawn_weights.tag',
  +
}
 
),
 
),
-- groupBy = 'mods._pageID, mod_stats.id, spawn_weights.tag',
+
field='mods._pageName',
  +
keep_id_field=true,
  +
append_id_field=true,
 
}
 
}
  +
 
  +
-- Get a sorted list that only has unique page names:
-- Query cargo rows:
 
  +
tpl_args.results_unique = {{}}
local results = m_util.cargo.query(tables, fields, query, args)
 
  +
for i, v in ipairs(tpl_args.results) do
 
  +
tpl_args.results_unique[i] = tpl_args.results[v][1]
-- Create own list for each cargo table and group by page name:
 
tpl_args.tbl = {}
 
for _,v in ipairs(tables) do
 
tpl_args.tbl[v] = {}
 
 
end
 
end
  +
 
tpl_args.results_unique = {}
 
local hash = {}
 
for _,v in ipairs(results) do
 
for ii, vv in pairs(tpl_args.tbl) do
 
if tpl_args.tbl[ii][v['mods._pageName']] == nil then
 
tpl_args.tbl[ii][v['mods._pageName']] = {}
 
end
 
local n = #tpl_args.tbl[ii][v['mods._pageName']] or 0
 
tpl_args.tbl[ii][v['mods._pageName']][n+1] = v
 
 
-- Get a sorted list that only has unique page names:
 
if hash[v['mods._pageName']] ~= true then
 
local m = #tpl_args.results_unique
 
tpl_args.results_unique[m+1] = v
 
hash[v['mods._pageName']] = true
 
end
 
end
 
end
 
 
 
-- Helpful error handling:
 
-- Helpful error handling:
 
local err_tbl = {
 
local err_tbl = {
 
{
 
{
bool = #results == 0,
+
bool = #tpl_args.results == 0,
 
disp = {
 
disp = {
 
i18n.errors.no_results,
 
i18n.errors.no_results,
Line 743: Line 801:
 
disp = {
 
disp = {
 
string.gsub(
 
string.gsub(
i18n.errors.incorrect_modid,
+
i18n.errors.incorrect_modid,
 
'%%s',
 
'%%s',
 
tpl_args.modid,
 
tpl_args.modid,
Line 752: Line 810:
 
},
 
},
 
}
 
}
for _,v in ipairs(err_tbl) do
+
for _, v in ipairs(err_tbl) do
 
if v.bool then
 
if v.bool then
 
local cats = {'Pages with modifier link errors'}
 
local cats = {'Pages with modifier link errors'}
Line 760: Line 818:
 
end
 
end
 
end
 
end
  +
 
 
-- Display formats:
 
-- Display formats:
 
local display = {
 
local display = {
 
abbr = {
 
abbr = {
 
display = function(tpl_args, frame)
 
display = function(tpl_args, frame)
local stat_text_colored = m_util.html.poe_color(
+
local name = m_util.html.poe_color(
'mod',
+
'mod',
 
string.format(
 
string.format(
'[[%s|%s]]',
+
'[[%s|%s]]',
tpl_args.results_unique[1]['mods._pageName'],
+
tpl_args.results_unique[1]['mods._pageName'],
 
tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
 
tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
 
)
 
)
 
)
 
)
  +
 
local out = m_util.html.tooltip(
+
local tooltip_table = {
stat_text_colored,
+
m_util.html.poe_color(
tpl_args.results_unique[1]['mods.stat_text_raw'],
+
'mod',
  +
tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
class
 
)
+
),
return out
+
m_util.html.poe_color(
  +
'help',
  +
m_game.constants.mod.generation_types[
  +
tonumber(
  +
tpl_args.results_unique[1]['mods.generation_type']
  +
)
  +
].full
  +
),
  +
-- m_util.html.poe_color(
  +
-- 'help',
  +
-- table.concat(
  +
-- m_util.string.split(tpl_args.results_unique[1]['mods.tags'] or '', ','),
  +
-- ', '
  +
-- )
  +
-- ),
  +
m_util.html.poe_color(
  +
'normal',
  +
tpl_args.results_unique[1]['mods.stat_text_raw']
  +
),
  +
}
  +
  +
local tt_tbl_fltrd = {}
  +
for _,v in ipairs(tooltip_table) do
  +
if v ~= nil and v ~= '' then
  +
tt_tbl_fltrd[#tt_tbl_fltrd+1] = v
  +
end
  +
end
  +
local tooltip = table.concat(tt_tbl_fltrd, '<br>')
  +
  +
return m_util.html.tooltip(name, tooltip, class)
 
end,
 
end,
 
},
 
},
Line 787: Line 874:
 
'%s - %s (%s)',
 
'%s - %s (%s)',
 
m_util.html.poe_color(
 
m_util.html.poe_color(
'mod',
+
'mod',
 
string.format(
 
string.format(
'[[%s|%s]]',
+
'[[%s|%s]]',
tpl_args.results_unique[1]['mods._pageName'],
+
tpl_args.results_unique[1]['mods._pageName'],
 
tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
 
tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
 
)
 
)
 
),
 
),
 
m_util.html.poe_color(
 
m_util.html.poe_color(
'mod',
+
'mod',
 
string.gsub(
 
string.gsub(
tpl_args.results_unique[1]['mods.stat_text'],
+
tpl_args.results_unique[1]['mods.stat_text'],
'<br>',
+
'<br>',
 
', '
 
', '
 
)
 
)
Line 813: Line 900:
 
display = function(tpl_args, frame)
 
display = function(tpl_args, frame)
 
return m_util.html.poe_color(
 
return m_util.html.poe_color(
'mod',
+
'mod',
 
tpl_args.results_unique[1]['mods.stat_text']
 
tpl_args.results_unique[1]['mods.stat_text']
 
)
 
)
Line 821: Line 908:
 
display = function(tpl_args, frame)
 
display = function(tpl_args, frame)
 
local statid = {}
 
local statid = {}
for _,v in ipairs(tpl_args.tbl.mod_stats[tpl_args.results_unique[1]['mods._pageName']]) do
+
for _, v in ipairs(tpl_args.results[tpl_args.results_unique[1]['mods._pageName']]) do
 
statid[#statid+1] = v['mod_stats.id']
 
statid[#statid+1] = v['mod_stats.id']
 
if tpl_args.statid == v['mod_stats.id'] then
 
if tpl_args.statid == v['mod_stats.id'] then
Line 827: Line 914:
 
end
 
end
 
end
 
end
  +
 
 
if tpl_args.statid == nil then
 
if tpl_args.statid == nil then
 
return m_util.html.error(
 
return m_util.html.error(
 
{
 
{
 
msg = string.format(
 
msg = string.format(
i18n.errors.undefined_statid,
+
i18n.errors.undefined_statid,
 
table.concat(statid, ', ')
 
table.concat(statid, ', ')
 
)
 
)
Line 843: Line 930:
 
display = function(tpl_args, frame)
 
display = function(tpl_args, frame)
 
local statid = {}
 
local statid = {}
for _,v in ipairs(tpl_args.tbl.mod_stats[tpl_args.results_unique[1]['mods._pageName']]) do
+
for _, v in ipairs(tpl_args.results[tpl_args.results_unique[1]['mods._pageName']]) do
 
statid[#statid+1] = v['mod_stats.id']
 
statid[#statid+1] = v['mod_stats.id']
 
if tpl_args.statid == v['mod_stats.id'] then
 
if tpl_args.statid == v['mod_stats.id'] then
Line 849: Line 936:
 
end
 
end
 
end
 
end
  +
 
 
if tpl_args.statid == nil then
 
if tpl_args.statid == nil then
 
return m_util.html.error(
 
return m_util.html.error(
 
{
 
{
 
msg = string.format(
 
msg = string.format(
i18n.errors.undefined_statid,
+
i18n.errors.undefined_statid,
 
table.concat(statid, ', ')
 
table.concat(statid, ', ')
 
)
 
)
Line 863: Line 950:
 
},
 
},
 
}
 
}
  +
 
 
return display[tpl_args.display or 'abbr'].display(tpl_args, frame)
 
return display[tpl_args.display or 'abbr'].display(tpl_args, frame)
 
end
 
end
Line 873: Line 960:
 
-- Drop down list
 
-- Drop down list
 
-- ---------------------------------------------------------------------
 
-- ---------------------------------------------------------------------
 
function h.get_mod_domain(cargo_query)
 
--[[
 
Gets the mod domain based on the item class.
 
]]
 
 
local out = cargo_query
 
local mod_domains = m_game.constants.mod.domains
 
 
-- Set the item class as key and the corresponding mod domain as
 
-- value:
 
local class_to_domain = {
 
['Life Flasks']=2,
 
['Mana Flasks']=2,
 
['Hybrid Flasks']=2,
 
['Utility Flasks']=2,
 
['Critical Utility Flasks']=2,
 
['Maps']=5,
 
['Jewel']=10,
 
['Leaguestones']=12,
 
['Abyss Jewel']=13,
 
}
 
 
for i,_ in ipairs(out) do
 
-- Get the domain, if it's not defined in the table assume it's
 
-- in the item domain.
 
out[i]['items.domain'] = class_to_domain[out[i]['items.class']] or 1
 
 
-- Convert the mod domain number to understandable text:
 
out[i]['items.domain_text'] = mod_domains[out[i]['items.domain']]['short_lower']
 
end
 
 
return out
 
end
 
 
function h.get_item_tags(frame)
 
--[[
 
This function queries for the tags of a specific item.
 
]]
 
 
-- Get template arguments:
 
local tpl_args = getArgs(frame, {parentFirst=true})
 
local frame = m_util.misc.get_frame(frame)
 
 
-- Format the cargo query:
 
tpl_args.q_tables = 'items'
 
tpl_args.q_fields = 'items.name, items.tags, items.class, items.inventory_icon, items.html, items._pageName'
 
local tbl = {
 
{tpl_args.page, 'items._pageName = "%s"'},
 
{tpl_args.item, 'items.name = "%s"'},
 
}
 
for _,v in ipairs(tbl) do
 
if v[1] ~= nil then
 
condition = string.format(v[2], v[1])
 
break
 
end
 
end
 
tpl_args.q_where = condition
 
tpl_args.q_groupBy = 'items._pageName'
 
tpl_args.q_orderBy = 'items.name'
 
 
-- Query mods with cargo:
 
results = h.cargo_query(tpl_args)
 
 
-- Find out what mod domain an item class is in:
 
results = h.get_mod_domain(results)
 
 
return results
 
end
 
   
 
function h.get_spawn_chance(frame)
 
function h.get_spawn_chance(frame)
 
--[[
 
--[[
Calculates the spawn chance of a set of mods that all have a
+
Calculates the spawn chance of a set of mods that all have a
 
spawn weight.
 
spawn weight.
  +
 
 
]]
 
]]
  +
 
 
-- Get template arguments:
 
-- Get template arguments:
 
local tpl_args = getArgs(frame, {parentFirst=true})
 
local tpl_args = getArgs(frame, {parentFirst=true})
 
local frame = m_util.misc.get_frame(frame)
 
local frame = m_util.misc.get_frame(frame)
  +
 
 
-- Get the table:
 
-- Get the table:
 
local tbl = tpl_args['tbl']
 
local tbl = tpl_args['tbl']
  +
 
 
-- Probabilities affecting the result besides the spawn weight:
 
-- Probabilities affecting the result besides the spawn weight:
local chance_multiplier = tonumber(tpl_args['chance_multiplier']) or 1
+
local chance_multiplier = tonumber(tpl_args['chance_multiplier']) or 1
  +
 
 
-- Total number of outcomes.
 
-- Total number of outcomes.
 
local N = 0
 
local N = 0
Line 965: Line 983:
 
N = N + tbl[i]['spawn_weights.weight']
 
N = N + tbl[i]['spawn_weights.weight']
 
end
 
end
  +
 
 
for i,_ in ipairs(tbl) do
 
for i,_ in ipairs(tbl) do
 
-- Number of ways it can happen:
 
-- Number of ways it can happen:
Line 972: Line 990:
 
-- Truncated value:
 
-- Truncated value:
 
tbl[i]['spawn_weights.chance'] = string.format(
 
tbl[i]['spawn_weights.chance'] = string.format(
"%0.2f%%",
+
"%0.2f%%",
 
n/N * chance_multiplier*100
 
n/N * chance_multiplier*100
 
)
 
)
end
+
end
  +
 
 
return tbl
 
return tbl
 
end
 
end
  +
 
 
function p.drop_down_table(frame)
 
function p.drop_down_table(frame)
 
--[[
 
--[[
This function queries mods that can spawn on an item. It compares
+
This function queries mods that can spawn on an item. It compares
the item tags and the spawn weight tags. If there's a match and
+
the item tags and the spawn weight tags. If there's a match and
the spawn weight is larger than zero, then that mod is added to a
+
the spawn weight is larger than zero, then that mod is added to a
 
drop down list.
 
drop down list.
  +
 
 
To Do:
 
To Do:
 
* Add support to:
 
* Add support to:
 
* Forsaken masters
 
* Forsaken masters
 
* Bestiary
 
* Bestiary
* Add a proper expand/collapse toggle for the entire header row so
+
* Add a proper expand/collapse toggle for the entire header row so
it reacts together with mw-collapsible.
+
it reacts together with mw-collapsible.
* Show Mod group in a better way perhaps:
+
* Show Mod group in a better way perhaps:
 
Mod group [Collapsible, default=Expanded]
 
Mod group [Collapsible, default=Expanded]
 
# to Damage [Collapsible, default=Collapsed]
 
# to Damage [Collapsible, default=Collapsed]
 
3 to Damage
 
3 to Damage
 
5 to Damage
 
5 to Damage
* Add a where condition that somehow filters out mods that obviously
+
* Add a where condition that somehow filters out mods that obviously
wont match with the item. spawn_weights.weight>0 isn't enough due
+
wont match with the item. spawn_weights.weight>0 isn't enough due
 
to possible edge cases.
 
to possible edge cases.
  +
* Consider moving the where variable to section table.
 
  +
 
+
Examples:
Examples:
 
 
Weapons
 
Weapons
p.drop_down_table{item = 'Rusted Hatchet', header = 'One Handed Axes'}
+
= p.drop_down_table{item='Rusted Hatchet', header='One Handed Axes'}
p.drop_down_table{item = 'Stone Axe', header = 'Two Handed Axes'}
+
= p.drop_down_table{item='Stone Axe', header='Two Handed Axes'}
   
 
Accessories
 
Accessories
p.drop_down_table{item = 'Amber Amulet', header = 'Amulets'}
+
= p.drop_down_table{item='Amber Amulet', header='Amulets'}
  +
 
 
Jewels
 
Jewels
p.drop_down_table{item = 'Cobalt Jewel', header = 'Jewels'}
+
= p.drop_down_table{item='Cobalt Jewel', header='Jewels'}
  +
 
 
Armour
 
Armour
p.drop_down_table{item = 'Plate Vest', header = 'Body armours'}
+
= p.drop_down_table{item='Plate Vest', header='Body armours'}
  +
 
 
Boots
 
Boots
p.drop_down_table{item = 'Iron Greaves', header = 'Boots'}
+
= p.drop_down_table{item='Iron Greaves', header='Boots'}
  +
 
p.drop_down_table{
+
= p.drop_down_table{
item = 'Fishing Rod',
+
item='Fishing Rod',
header = 'FISH PLEASE',
+
header='FISH PLEASE',
item_tags = 'fishing_rod',
+
item_tags='fishing_rod',
extra_fields = 'Has spawn weight, Has spawn chance'
+
extra_fields='mods.tags'
 
}
 
}
  +
 
 
= p.drop_down_table{
 
= p.drop_down_table{
item = 'Fishing Rod',
+
item='Fishing Rod',
item_tags = 'axe, one_hand_weapon, onehand, weapon, default',
+
item_tags='axe, one_hand_weapon, onehand, weapon, default',
extra_item_tags = 'fishing_rod'
+
extra_item_tags='fishing_rod'
 
}
 
}
  +
 
 
= p.drop_down_table{
 
= p.drop_down_table{
item = 'Vaal Blade',
+
item='Vaal Blade',
 
}
 
}
  +
 
 
]]
 
]]
   
Line 1,043: Line 1,060:
 
local tpl_args = getArgs(frame, {parentFirst=true})
 
local tpl_args = getArgs(frame, {parentFirst=true})
 
local frame = m_util.misc.get_frame(frame)
 
local frame = m_util.misc.get_frame(frame)
  +
 
-- Get the items tags:
+
-- Format the cargo query:
  +
local where
tpl_args.get_item_tags = h.get_item_tags(tpl_args)[1]
 
  +
for _,v in ipairs({
 
  +
{tpl_args.page, 'items._pageName = "%s"'},
-- For some reason cargo queried item tags, are not comma-space
 
  +
{tpl_args.item, 'items.name = "%s"'},
-- separated any more.
 
  +
}) do
if tpl_args.item_tags ~= nil then
 
  +
if v[1] ~= nil then
tpl_args.item_tags = m_util.string.split(tpl_args.item_tags, ', ')
 
  +
where = string.format(v[2], v[1])
else
 
  +
break
tpl_args.item_tags = m_util.string.split(tpl_args.get_item_tags['items.tags'], ',')
 
end
 
if tpl_args.extra_item_tags then
 
local extra_item_tags = m_util.string.split(tpl_args.extra_item_tags, ', ')
 
for _,v in ipairs(extra_item_tags) do
 
tpl_args.item_tags[#tpl_args.item_tags+1] = v
 
 
end
 
end
 
end
 
end
  +
 
  +
local item_info = m_cargo.query(
  +
{'items'},
  +
{
  +
'items.name',
  +
'items.tags',
  +
'items.class_id',
  +
'items.inventory_icon',
  +
'items.html',
  +
'items.release_version',
  +
'items._pageName'
  +
},
  +
{
  +
where=where,
  +
groupBy='items._pageName',
  +
orderBy='items.name, items.release_version DESC',
  +
}
  +
)[1]
  +
  +
-- Set the item class as key and the corresponding mod domain as
  +
-- value:
  +
local class_to_domain = {
  +
['LifeFlask']=2,
  +
['ManaFlask']=2,
  +
['HybridFlask']=2,
  +
['UtilityFlask']=2,
  +
['UtilityFlaskCritical']=2,
  +
['Map']=5,
  +
['Jewel']=10,
  +
['Leaguestone']=12,
  +
['AbyssJewel']=13,
  +
}
  +
-- Get the domain, if it's not defined in the table assume it's
  +
-- in the item domain.
  +
item_info['items.domain'] = tonumber(tpl_args.domain) or class_to_domain[item_info['items.class_id']] or 1
  +
  +
-- Convert the mod domain number to understandable text:
  +
item_info['items.domain_text'] = m_game.constants.mod.domains[item_info['items.domain']]['short_lower']
  +
  +
-- Format item tags:
  +
tpl_args.item_tags = m_util.string.split_args(
  +
tpl_args.item_tags or item_info['items.tags'],
  +
{sep=',%s*'}
  +
)
  +
for _,v in ipairs(m_util.string.split_args(tpl_args.extra_item_tags, {sep=',%s*'})) do
  +
tpl_args.item_tags[#tpl_args.item_tags+1] = v
  +
end
  +
  +
-- Format extra fields:
  +
local extra_fields = m_util.string.split_args(tpl_args.extra_fields, {sep=',%s*'})
  +
  +
  +
-- Get tags that are appended to special items:
  +
local elder_tag = m_game.constants.item.classes[item_info['items.class_id']]['elder_tag']
  +
local shaper_tag = m_game.constants.item.classes[item_info['items.class_id']]['shaper_tag']
  +
local crusader_tag = m_game.constants.item.classes[item_info['items.class_id']]['crusader_tag']
  +
local eyrie_tag = m_game.constants.item.classes[item_info['items.class_id']]['eyrie_tag']
  +
local basilisk_tag = m_game.constants.item.classes[item_info['items.class_id']]['basilisk_tag']
  +
local adjudicator_tag = m_game.constants.item.classes[item_info['items.class_id']]['adjudicator_tag']
  +
 
-- Create drop down lists in these sections and query in these
 
-- Create drop down lists in these sections and query in these
 
-- generation types.
 
-- generation types.
local section = {}
+
local section = {
section = {
 
 
{
 
{
 
header = i18n.drop_down_table.prefix,
 
header = i18n.drop_down_table.prefix,
 
where = function(tpl_args, frame, value)
 
where = function(tpl_args, frame, value)
 
local where = {}
 
local where = {}
for _, item_tag in ipairs(tpl_args.item_tags) do
+
for _, item_tag in ipairs(tpl_args.item_tags) do
 
where[#where+1] = string.format(
 
where[#where+1] = string.format(
'(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
+
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
 
item_tag,
 
item_tag,
 
1,
 
1,
tpl_args.get_item_tags['items.domain']
+
item_info['items.domain']
 
)
 
)
 
end
 
end
  +
 
return table.concat(where, ' OR ')
+
return table.concat(where, ' OR ')
 
end,
 
end,
 
},
 
},
Line 1,085: Line 1,160:
 
where = function(tpl_args, frame, value)
 
where = function(tpl_args, frame, value)
 
local where = {}
 
local where = {}
for _, item_tag in ipairs(tpl_args.item_tags) do
+
for _, item_tag in ipairs(tpl_args.item_tags) do
 
where[#where+1] = string.format(
 
where[#where+1] = string.format(
'(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
+
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
 
item_tag,
 
item_tag,
 
2,
 
2,
tpl_args.get_item_tags['items.domain']
+
item_info['items.domain']
 
)
 
)
 
end
 
end
  +
 
return table.concat(where, ' OR ')
+
return table.concat(where, ' OR ')
 
end,
 
end,
 
},
 
},
 
{
 
{
header = i18n.drop_down_table.corrupted,
+
tags = {elder_tag},
  +
header = i18n.drop_down_table.elder_prefix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
elder_tag,
  +
1,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
tags = {elder_tag},
  +
header = i18n.drop_down_table.elder_suffix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
elder_tag,
  +
2,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
tags = {shaper_tag},
  +
header = i18n.drop_down_table.shaper_prefix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
shaper_tag,
  +
1,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
tags = {shaper_tag},
  +
header = i18n.drop_down_table.shaper_suffix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
shaper_tag,
  +
2,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
header = i18n.drop_down_table.delve_prefix,
 
where = function(tpl_args, frame, value)
 
where = function(tpl_args, frame, value)
 
local where = {}
 
local where = {}
for _, item_tag in ipairs(tpl_args.item_tags) do
+
for _, item_tag in ipairs(tpl_args.item_tags) do
 
where[#where+1] = string.format(
 
where[#where+1] = string.format(
'(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
+
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
 
item_tag,
 
item_tag,
5,
+
1,
tpl_args.get_item_tags['items.domain']
+
16
 
)
 
)
 
end
 
end
  +
 
return table.concat(where, ' OR ')
+
return table.concat(where, ' OR ')
 
end,
 
end,
chance_multiplier = 1/4, -- See Vaal orb, for the 4 possible events.
 
 
},
 
},
 
{
 
{
header = i18n.drop_down_table.elder_prefix,
+
header = i18n.drop_down_table.delve_suffix,
 
where = function(tpl_args, frame, value)
 
where = function(tpl_args, frame, value)
local suffix = '_elder'
 
 
local where = {}
 
local where = {}
for i, item_tag in ipairs(tpl_args.item_tags) do
+
for _, item_tag in ipairs(tpl_args.item_tags) do
if not string.match(item_tag, suffix) then
 
tpl_args.item_tags[i] = item_tag .. suffix
 
end
 
 
where[#where+1] = string.format(
 
where[#where+1] = string.format(
'(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
+
[[
tpl_args.item_tags[i],
+
(spawn_weights.tag="%s"
1,
+
AND mods.generation_type=%s
tpl_args.get_item_tags['items.domain']
+
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
item_tag,
  +
2,
  +
16
 
)
 
)
 
end
 
end
  +
 
return table.concat(where, ' OR ')
+
return table.concat(where, ' OR ')
 
end,
 
end,
 
},
 
},
 
{
 
{
header = i18n.drop_down_table.elder_suffix,
+
tags = {crusader_tag},
  +
header = i18n.drop_down_table.crusader_prefix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
crusader_tag,
  +
1,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
tags = {crusader_tag},
  +
header = i18n.drop_down_table.crusader_suffix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
crusader_tag,
  +
2,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
tags = {eyrie_tag},
  +
header = i18n.drop_down_table.eyrie_prefix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
eyrie_tag,
  +
1,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
tags = {eyrie_tag},
  +
header = i18n.drop_down_table.eyrie_suffix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
eyrie_tag,
  +
2,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
tags = {basilisk_tag},
  +
header = i18n.drop_down_table.basilisk_prefix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
basilisk_tag,
  +
1,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
tags = {basilisk_tag},
  +
header = i18n.drop_down_table.basilisk_suffix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
basilisk_tag,
  +
2,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
tags = {adjudicator_tag},
  +
header = i18n.drop_down_table.adjudicator_prefix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
adjudicator_tag,
  +
1,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
tags = {adjudicator_tag},
  +
header = i18n.drop_down_table.adjudicator_suffix,
  +
where = function(tpl_args, frame, value)
  +
return string.format(
  +
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
adjudicator_tag,
  +
2,
  +
item_info['items.domain']
  +
)
  +
end,
  +
},
  +
{
  +
header = i18n.drop_down_table.veiled_prefix,
 
where = function(tpl_args, frame, value)
 
where = function(tpl_args, frame, value)
local suffix = '_elder'
 
 
local where = {}
 
local where = {}
for i, item_tag in ipairs(tpl_args.item_tags) do
+
for _, item_tag in ipairs(tpl_args.item_tags) do
if not string.match(item_tag, suffix) then
 
tpl_args.item_tags[i] = item_tag .. suffix
 
end
 
 
where[#where+1] = string.format(
 
where[#where+1] = string.format(
'(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
+
[[
tpl_args.item_tags[i],
+
(spawn_weights.tag="%s"
2,
+
AND mods.generation_type=%s
tpl_args.get_item_tags['items.domain']
+
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL
  +
AND mods.id LIKE "%%Master%%")
  +
]],
  +
item_tag,
  +
1,
  +
9
 
)
 
)
 
end
 
end
  +
 
return table.concat(where, ' OR ')
+
return table.concat(where, ' OR ')
 
end,
 
end,
 
},
 
},
 
{
 
{
header = i18n.drop_down_table.shaper_prefix,
+
header = i18n.drop_down_table.veiled_suffix,
 
where = function(tpl_args, frame, value)
 
where = function(tpl_args, frame, value)
local suffix = '_shaper'
 
 
local where = {}
 
local where = {}
for i, item_tag in ipairs(tpl_args.item_tags) do
+
for _, item_tag in ipairs(tpl_args.item_tags) do
if not string.match(item_tag, suffix) then
 
tpl_args.item_tags[i] = item_tag .. suffix
 
end
 
 
where[#where+1] = string.format(
 
where[#where+1] = string.format(
'(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
+
[[
tpl_args.item_tags[i],
+
(spawn_weights.tag="%s"
1,
+
AND mods.generation_type=%s
tpl_args.get_item_tags['items.domain']
+
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL
  +
AND mods.id LIKE "%%Master%%")
  +
]],
  +
item_tag,
  +
2,
  +
9
 
)
 
)
 
end
 
end
  +
 
return table.concat(where, ' OR ')
+
return table.concat(where, ' OR ')
 
end,
 
end,
 
},
 
},
 
{
 
{
header = i18n.drop_down_table.shaper_suffix,
+
header = i18n.drop_down_table.corrupted,
 
where = function(tpl_args, frame, value)
 
where = function(tpl_args, frame, value)
local suffix = '_shaper'
 
 
local where = {}
 
local where = {}
for i, item_tag in ipairs(tpl_args.item_tags) do
+
for _, item_tag in ipairs(tpl_args.item_tags) do
if not string.match(item_tag, suffix) then
 
tpl_args.item_tags[i] = item_tag .. suffix
 
end
 
 
where[#where+1] = string.format(
 
where[#where+1] = string.format(
'(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
+
[[
tpl_args.item_tags[i],
+
(spawn_weights.tag="%s"
2,
+
AND mods.generation_type=%s
tpl_args.get_item_tags['items.domain']
+
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
  +
item_tag,
  +
5,
  +
item_info['items.domain']
 
)
 
)
 
end
 
end
  +
 
return table.concat(where, ' OR ')
+
return table.concat(where, ' OR ')
 
end,
 
end,
  +
chance_multiplier = 1/4, -- See Vaal orb, for the 4 possible events.
  +
is_implicit = true,
 
},
 
},
 
{
 
{
Line 1,198: Line 1,494:
 
where = function(tpl_args, frame, value)
 
where = function(tpl_args, frame, value)
 
local where = {}
 
local where = {}
for _, item_tag in ipairs(tpl_args.item_tags) do
+
for _, item_tag in ipairs(tpl_args.item_tags) do
 
where[#where+1] = string.format(
 
where[#where+1] = string.format(
'(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
+
[[
  +
(spawn_weights.tag="%s"
  +
AND mods.generation_type=%s
  +
AND mods.domain=%s
  +
AND mods.stat_text IS NOT NULL)
  +
]],
 
item_tag,
 
item_tag,
 
10,
 
10,
tpl_args.get_item_tags['items.domain']
+
item_info['items.domain']
 
)
 
)
 
end
 
end
  +
 
return table.concat(where, ' OR ')
+
return table.concat(where, ' OR ')
 
end,
 
end,
  +
is_implicit = true,
 
},
 
},
 
}
 
}
  +
 
  +
-- Save the original tag format:
 
  +
local item_tags_orig = {}
  +
for i,v in ipairs(tpl_args.item_tags) do
  +
item_tags_orig[i] = v
  +
end
  +
  +
local item_mods = {}
  +
local mod_group_counter = {}
  +
mod_group_counter['all'] = {}
  +
local extra_fieldss = {}
  +
local table_index_base = -1
  +
for _, sctn in ipairs(section) do
  +
item_mods[sctn['header']] = {}
  +
  +
-- Preallocate the mod group counter, implicit and explicit mods
  +
-- are counted separetely because they can spawn together:
  +
mod_group_counter[sctn['header']] = {}
  +
local adj = 'explicit'
  +
if sctn['is_implicit'] then
  +
adj = 'implicit'
  +
end
  +
for _, header in ipairs({sctn['header'], 'all'}) do
  +
if mod_group_counter[header][adj] == nil then
  +
mod_group_counter[header][adj] = {}
  +
end
  +
end
  +
  +
local continue = true
  +
local current_tags
  +
if sctn['tags'] then
  +
-- some item classes do not have shaper/elder items, so the table
  +
-- will not contain any tags:
  +
if #sctn['tags'] == 0 then
  +
continue = false
  +
else
  +
current_tags = sctn['tags']
  +
end
  +
else
  +
current_tags = {}
  +
-- Reset to original tags:
  +
for i,v in ipairs(item_tags_orig) do
  +
current_tags[i] = v
  +
end
  +
end
  +
  +
if continue then
  +
-- Cargo preparation:
  +
local q_fields = {
  +
'mods._pageName',
  +
'mods.name',
  +
'mods.id',
  +
'mods.required_level',
  +
'mods.generation_type',
  +
'mods.domain',
  +
'mods.mod_group',
  +
'mods.mod_type',
  +
'mods.stat_text',
  +
'mods.stat_text_raw',
  +
'mods.tags',
  +
'mod_stats.id',
  +
'spawn_weights.tag',
  +
'spawn_weights.weight',
  +
'spawn_weights.ordinal',
  +
'spawn_weights._pageName'
  +
}
  +
for i, v in ipairs(extra_fields) do
  +
q_fields[#q_fields+1] = v
  +
end
  +
  +
-- Query mods and map the results to the pagename:
  +
local results = m_cargo.map_results_to_id{
  +
results=m_cargo.query(
  +
{'mods', 'spawn_weights', 'mod_stats'},
  +
q_fields,
  +
{
  +
join = [[
  +
mods._pageName=spawn_weights._pageName,
  +
mods._pageName=mod_stats._pageName
  +
]],
  +
where = sctn['where'](tpl_args, frame, value),
  +
groupBy = [[
  +
mods._pageName,
  +
spawn_weights.tag,
  +
spawn_weights.weight
  +
]],
  +
orderBy = [[
  +
mods.generation_type,
  +
mods.mod_group,
  +
mods.mod_type,
  +
mods.required_level,
  +
mods._pageName,
  +
spawn_weights.ordinal
  +
]],
  +
}
  +
),
  +
field='mods._pageName',
  +
keep_id_field=true,
  +
append_id_field=true,
  +
}
  +
  +
if #results > 0 then
  +
-- Loop through all found modifiers:
  +
local last
  +
for _, id in ipairs(results) do
  +
-- Loop through all the modifier tags until they match
  +
-- the item tags:
  +
local j = 0
  +
local tag_match_stop
  +
repeat
  +
j = j+1
  +
local mod_tag = results[id][j]['spawn_weights.tag']
  +
local mod_tag_weight = tonumber(
  +
results[id][j]['spawn_weights.weight']
  +
)
  +
  +
-- Loop through the item tags until it matches the
  +
-- spawn weight tag and the mod tag has a value larger than
  +
-- zero:
  +
local y = 0
  +
local tag_match_add = false
  +
repeat
  +
y = y+1
  +
tag_match_stop = ((mod_tag == current_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (results[id][j] == nil)
  +
tag_match_add = (mod_tag == current_tags[y]) and ((mod_tag_weight or -1) > 0)
  +
until tag_match_stop or y == #current_tags
  +
  +
-- If there's a match then save that mod and other
  +
-- interesting information:
  +
if tag_match_add then
  +
  +
-- Assume that the mod is global then go through
  +
-- all the stat ids and check if any of the
  +
-- stats are local:
  +
local mod_scope = 'Global'
  +
for _, vv in ipairs(results[id]) do
  +
if vv['mod_stats.id']:find('.*local.*') ~= nil then
  +
mod_scope = 'Local'
  +
end
  +
end
  +
  +
-- Save the matching modifier tag:
  +
local a = #item_mods[sctn['header']]
  +
item_mods[sctn['header']][a+1] = results[id][j]
  +
  +
-- Save other interesting fields:
  +
item_mods[sctn['header']][a+1]['mods.scope'] = mod_scope
  +
item_mods[sctn['header']][a+1]['spawn_weight.idx_match'] = j
  +
item_mods[sctn['header']][a+1]['mods.add'] = tag_match_add
  +
item_mods[sctn['header']][a+1]['mods.stop'] = tag_match_stop
  +
  +
-- Count the mod groups:
  +
local group = item_mods[sctn['header']][a+1]['mods.mod_group'] or 'nil_group'
  +
for _, header in ipairs({sctn['header'], 'all'}) do
  +
if mod_group_counter[header][adj][group] == nil then
  +
mod_group_counter[header][adj][group] = {}
  +
end
  +
local tp = results[id][j]['mods.mod_type']
  +
local bef = mod_group_counter[header][adj][group][tp] or 0
  +
mod_group_counter[header][adj][group][tp] = 1 + bef
  +
end
  +
end
  +
until tag_match_stop
  +
end
  +
  +
-- If the user wants to see the spawn chance then do the
  +
-- calculations and save that result as well:
  +
if tpl_args.spawn_chance ~= nil then
  +
extra_fields[#extra_fields+1] = 'spawn_weights.chance'
  +
item_mods[sctn['header']] = h.get_spawn_chance{
  +
tbl = item_mods[sctn['header']],
  +
chance_multiplier = sctn['chance_multiplier']
  +
}
  +
end
  +
  +
extra_fieldss[sctn['header']] = extra_fields
  +
end
  +
end
  +
end
  +
  +
  +
--
  +
-- Display the item mods
  +
--
  +
 
-- Introductory text:
 
-- Introductory text:
 
local out = {}
 
local out = {}
 
out[#out+1] = string.format(
 
out[#out+1] = string.format(
'==%s== \n',
+
'==%s== \n',
 
tpl_args['header'] or table.concat(tpl_args.item_tags, ', ')
 
tpl_args['header'] or table.concat(tpl_args.item_tags, ', ')
 
)
 
)
out[#out+1] = string.format(
+
local expand_button = string.format(
 
'<div style="float: right; text-align:center"><div class="mw-collapsible-collapse-all" style="cursor:pointer;">[%s]</div><hr><div class="mw-collapsible-expand-all" style="cursor:pointer;">[%s]</div></div>',
 
'<div style="float: right; text-align:center"><div class="mw-collapsible-collapse-all" style="cursor:pointer;">[%s]</div><hr><div class="mw-collapsible-expand-all" style="cursor:pointer;">[%s]</div></div>',
 
i18n.drop_down_table.collapse_all,
 
i18n.drop_down_table.collapse_all,
 
i18n.drop_down_table.expand_all
 
i18n.drop_down_table.expand_all
 
)
 
)
out[#out+1] = string.format('%s %s.<br><br><br>',
+
out[#out+1] = expand_button
  +
out[#out+1] = string.format('%s %s.<br><br><br>',
i18n.drop_down_table.table_intro,
 
  +
i18n.drop_down_table.table_intro,
 
f_item_link{
 
f_item_link{
page=tpl_args.get_item_tags['items._pageName'],
+
page=item_info['items._pageName'],
name=tpl_args.get_item_tags['items.name'],
+
name=item_info['items.name'],
inventory_icon=tpl_args.get_item_tags['items.inventory_icon'] or '',
+
inventory_icon=item_info['items.inventory_icon'] or '',
html=tpl_args.get_item_tags['items.html'] or '',
+
html=item_info['items.html'] or '',
 
skip_query=true
 
skip_query=true
 
}
 
}
 
)
 
)
  +
 
-- Save the original tag format:
+
-- Loop through the sections:
local item_tags_orig = {}
 
for i,v in ipairs(tpl_args.item_tags) do
 
item_tags_orig[i] = v
 
end
 
 
local item_mods = {}
 
local mod_group_counter = {}
 
mod_group_counter['all'] = {}
 
local tableIndex = -1
 
 
for _, sctn in ipairs(section) do
 
for _, sctn in ipairs(section) do
  +
local extra_fields = extra_fieldss[sctn['header']]
-- Reset to original tags:
 
for i,v in ipairs(item_tags_orig) do
+
local adj = 'explicit'
tpl_args.item_tags[i] = v
+
if sctn['is_implicit'] then
  +
adj = 'implicit'
 
end
 
end
  +
 
 
-- Create html container:
 
-- Create html container:
 
local container = mw.html.create('div')
 
local container = mw.html.create('div')
 
:attr('style', 'vertical-align:top; display:inline-block;')
 
:attr('style', 'vertical-align:top; display:inline-block;')
  +
 
-- Cargo preparation:
+
-- Create the drop down table with <table></table>:
  +
local headers = container
tpl_args.q_tables = 'mods, spawn_weights, mod_stats'
 
  +
if #item_mods[sctn['header']] > 0 then
tpl_args.q_fields = 'mods.name, mods.id, mods.required_level, mods.generation_type, mods.domain, mods.mod_group, mods.mod_type, mods.stat_text, mods._pageName, mod_stats.id, spawn_weights.tag, spawn_weights.weight, spawn_weights.ordinal, spawn_weights._pageName'
 
  +
headers
tpl_args.q_join = 'mods._pageName=spawn_weights._pageName, mods._pageName=mod_stats._pageName'
 
  +
:tag('h3')
tpl_args.q_where = sctn['where'](tpl_args, frame, value)
 
  +
:wikitext(string.format('%s', sctn['header']))
tpl_args.q_groupBy = 'mods._pageName, spawn_weights.tag, spawn_weights.weight'
 
  +
:done()
tpl_args.q_orderBy = 'mods.generation_type, mods.mod_group, mods.mod_type, mods._pageName, mods.required_level, spawn_weights.ordinal'
 
+
:done()
local extra_fields = {}
 
if tpl_args.extra_fields ~= nil then
 
extra_fields = m_util.string.split(tpl_args.extra_fields, ', ')
 
tpl_args.fields = string.format(
 
'%s, %s',
 
tpl_args.fields,
 
table.concat(extra_fields, ', ')
 
)
 
 
end
 
end
  +
 
-- Query mods:
+
local total_mod_groups = 0
  +
for _ in pairs(mod_group_counter[sctn['header']][adj]) do
results = h.cargo_query(tpl_args)
 
  +
total_mod_groups = 1+total_mod_groups
 
-- Create own list for spawn weights and group by page name:
 
local spawn_weights = {}
 
local results_unique = {}
 
local hash = {}
 
for _,v in ipairs(results) do
 
if spawn_weights[v['mods._pageName']] == nil then
 
spawn_weights[v['mods._pageName']] = {}
 
end
 
local n = #spawn_weights[v['mods._pageName']] or 0
 
spawn_weights[v['mods._pageName']][n+1] = v
 
 
-- Get a sorted list that only has unique page names:
 
if hash[v['mods._pageName']] ~= true then
 
results_unique[#results_unique+1] = v
 
hash[v['mods._pageName']] = true
 
end
 
 
end
 
end
 
if #results_unique > 0 then
 
item_mods[sctn['header']] = {}
 
mod_group_counter[sctn['header']] = {}
 
 
-- Loop through all found modifiers:
 
local last
 
for _, v in ipairs(results_unique) do
 
local pagename = v['spawn_weights._pageName']
 
 
-- Loop through all the modifier tags until they match
 
-- the item tags:
 
local j = 0
 
local tag_match_stop
 
repeat
 
j = j+1
 
local mod_tag = spawn_weights[pagename][j]['spawn_weights.tag']
 
local mod_tag_weight = tonumber(
 
spawn_weights[pagename][j]['spawn_weights.weight']
 
)
 
   
-- Loop through the item tags until it matches the
+
-- Loop through and add all matching mods to the <table>.
  +
local tbl, last_group, last_type
-- spawn weight tag and the mod tag has a value larger than
 
  +
for _, rows in ipairs(item_mods[sctn['header']]) do
-- zero:
 
  +
local y = 0
 
local tag_match_add = false
+
-- If the last mod group is different to the current
repeat
+
-- mod group then assume the mod isn't related and start
y = y+1
+
-- a new drop down list, if there's only one mod group
  +
-- then use mod type instead:
tag_match_stop = ((mod_tag == tpl_args.item_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (spawn_weights[pagename][j] == nil)
 
  +
if rows['mods.mod_group'] ~= last_group or (total_mod_groups == 1 and rows['mods.mod_type'] ~= last_type) then
tag_match_add = (mod_tag == tpl_args.item_tags[y]) and ((mod_tag_weight or -1) > 0)
 
until tag_match_stop or y == #tpl_args.item_tags
+
-- Check through all the mods and see if there are
+
-- multiple mod types within the same mod group:
-- If there's a match then save that mod and other
+
local count = {}
-- interesting information:
+
for _, n in ipairs(item_mods[sctn['header']]) do
  +
if tag_match_add then
 
+
-- If the mod has the same mod group, then add
-- Assume that the mod is global then go through
+
-- the mod type to the counter. Only unique mod
-- all the stat ids and check if any of the
+
-- types matter so the number is just a dummy
-- stats are local:
+
-- value:
local mod_scope = 'Global'
+
if n['mods.mod_group'] == rows['mods.mod_group'] then
for _, vv in ipairs(spawn_weights[pagename]) do
+
count[n['mods.mod_type']] = 1
if vv['mod_stats.id']:find('.*local.*') ~= nil then
 
mod_scope = 'Local'
 
end
 
end
 
 
-- Save the matching modifier tag:
 
local a = #item_mods[sctn['header']]
 
item_mods[sctn['header']][a+1] = spawn_weights[pagename][j]
 
 
-- Save other interesting fields:
 
item_mods[sctn['header']][a+1]['mods.scope'] = mod_scope
 
item_mods[sctn['header']][a+1]['spawn_weight.idx_match'] = j
 
item_mods[sctn['header']][a+1]['mods.add'] = tag_match_add
 
item_mods[sctn['header']][a+1]['mods.stop'] = tag_match_stop
 
-- Mod group counter:
 
local group = item_mods[sctn['header']][a+1]['mods.mod_group'] or 'nil_group'
 
mod_group_counter[sctn['header']][group] = 1 + (mod_group_counter[sctn['header']][group] or 0)
 
mod_group_counter['all'][group] = 1 + (mod_group_counter['all'][group] or 0)
 
 
end
 
end
until tag_match_stop
+
end
  +
end
 
  +
-- Calculate how many unique mod types with the
 
-- If the user wants to see the spawn chance then do the
+
-- same mod group there are for all explicit or implicit
-- calculations and save that result as well:
+
-- sections since a mod with the same mod group can't
if tpl_args.spawn_chance ~= nil then
+
-- spawn. Doesn't matter if it's prefix or suffix.
extra_fields[#extra_fields+1] = 'spawn_weights.chance'
+
local number_of_mod_types = 0
item_mods[sctn['header']] = h.get_spawn_chance{
+
for _ in pairs(mod_group_counter['all'][adj][rows['mods.mod_group']]) do
tbl = item_mods[sctn['header']],
+
number_of_mod_types = 1 + number_of_mod_types
chance_multiplier = sctn['chance_multiplier']
+
end
  +
}
 
  +
-- If there are multiple unique mod types with the
end
 
  +
-- same mod group then change the style of the drop
 
-- Create the drop down table with <table></table>:
+
-- down list to indicate it, if there's only one
  +
-- mod group in the generation type then ignore it:
local headers = container
 
if #item_mods[sctn['header']] > 0 then
+
local table_index_mod_group
headers
+
if number_of_mod_types > 1 and total_mod_groups > 1 then
:tag('h3')
+
table_index_mod_group = table.concat(
:wikitext(string.format(
+
{string.byte(rows['mods.mod_group'], 1, #rows['mods.mod_group'])},
'%s',
+
''
sctn['header']
+
)
  +
  +
tbl_caption = string.format(
  +
'%s',
  +
m_util.html.poe_color(
  +
'stat',
  +
string.format(
  +
'%s %s',
  +
i18n.drop_down_table.mod_group,
  +
rows['mods.mod_group']
 
)
 
)
  +
) or ''
  +
)
  +
  +
else
  +
tbl_caption = string.format(
  +
'%s (%s)',
  +
m_util.html.poe_color(
  +
'mod',
  +
h.header(rows['mods.stat_text_raw'])
  +
) or '',
  +
rows['mods.scope']
  +
)
  +
end
  +
  +
-- Create a table index for handling the collapsible:
  +
table_index_base = table_index_base+1
  +
if table_index_mod_group ~= nil then
  +
table_index = table_index_mod_group
  +
else
  +
table_index = table_index_base
  +
end
  +
  +
-- Add class and style to the <table>:
  +
tbl = container:tag('table')
  +
tbl
  +
:attr('class', 'mw-collapsible mw-collapsed')
  +
:attr('style',
  +
'text-align:left; line-height:1.60em; width:810px;'
  +
)
  +
:tag('th')
  +
:attr('class',
  +
string.format('mw-customtoggle-%s', table_index)
  +
)
  +
:attr('style',
  +
'text-align:left; line-height:1.40em; border-bottom:1pt solid dimgrey;'
 
)
 
)
  +
:attr('colspan', '3' .. #extra_fields)
  +
:wikitext(tbl_caption)
 
:done()
 
:done()
 
:done()
 
:done()
 
end
 
end
  +
 
  +
-- If the mod has no name then use the mod type:
local total_mod_groups = 0
 
for k,v in pairs(mod_group_counter[sctn['header']]) do
+
local mod_name = rows['mods.name'] or ''
total_mod_groups = 1+total_mod_groups
+
if mod_name == '' then
  +
mod_name = rows['mods.mod_type']
 
end
 
end
  +
 
-- Loop through and add all matching mods to the <table>.
+
-- Check if there are any extra properties to show in
local tbl, last_group, last_type
+
-- the drop down list and then add a cell for that,
for _, rows in ipairs(item_mods[sctn['header']]) do
+
-- add this node at the end of the table row:
+
local td = mw.html.create('td')
  +
if extra_fields ~= nil then
-- If the last mod group is different to the current
 
-- mod group then assume the mod isn't related and start
+
for _, extra_field in ipairs(extra_fields) do
-- a new drop down list, if there's only one mod group
+
td
-- then use mod type instead:
+
:attr('width', '*')
  +
:wikitext(string.format(
if rows['mods.mod_group'] ~= last_group or (total_mod_groups == 1 and rows['mods.mod_type'] ~= last_type) then
 
-- Check through all the mods and see if there are
+
'%s:&nbsp;%s ',
-- multiple mod types within the same mod group:
+
extra_field,
local count = {}
+
rows[extra_field] or ''
for _, n in ipairs(item_mods[sctn['header']]) do
 
 
-- If the mod has the same mod group, then add
 
-- the mod type to the counter. Only unique mod
 
-- types matter so the number is just a dummy
 
-- value:
 
if n['mods.mod_group'] == rows['mods.mod_group'] then
 
count[n['mods.mod_type']] = 1
 
end
 
end
 
 
-- Calculate how many unique mod types with the
 
-- same mod group there are:
 
local number_of_mod_types = 0
 
for _ in pairs(count) do
 
number_of_mod_types = number_of_mod_types + 1
 
end
 
 
-- If there are multiple unique mod types with the
 
-- same mod group then change the style of the drop
 
-- down list to indicate it, if there's only one
 
-- mod group in the generation type then ignore it:
 
if number_of_mod_types > 1 and total_mod_groups > 1 then
 
tbl_caption = string.format(
 
'%s',
 
m_util.html.poe_color(
 
'mod',
 
string.format(
 
'%s %s',
 
i18n.drop_down_table.mod_group,
 
rows['mods.mod_group']
 
)
 
 
)
 
)
 
)
 
)
  +
:done()
  +
end
  +
end
   
else
+
-- Style mods.tags:
tbl_caption = string.format(
+
local mods_tags = table.concat(
'%s (%s)',
+
m_util.string.split_args(rows['mods.tags'], {sep=',%s*'}),
m_util.html.poe_color(
+
', '
'mod',
+
)
h.header(rows['mods.stat_text'])
+
if mods_tags ~= '' then
),
+
mods_tags = m_util.html.tooltip('*', mods_tags, class)
rows['mods.scope']
+
end
  +
)
 
end
+
-- Add a table row with the interesting properties that
+
-- modifier has:
-- Add class and style to the <table>:
+
tbl
tableIndex = tableIndex+1
+
:tag('tr')
tbl = container:tag('table')
+
:attr('class', 'mw-collapsible mw-collapsed')
tbl
+
:attr(
:attr('class', 'mw-collapsible mw-collapsed')
+
'id',
:attr('style',
+
string.format('mw-customcollapsible-%s', table_index)
'text-align:left; line-height:1.60em; width:810px;'
+
)
  +
:tag('td')
  +
:attr('width', '160')
  +
:wikitext(
  +
string.format(
  +
'&nbsp;&nbsp;&nbsp;[[%s|%s]]',
  +
rows['mods._pageName'],
  +
mod_name:gsub('%s', '&nbsp;')
  +
)
 
)
 
)
:tag('th')
+
:done()
:attr('class',
+
:tag('td')
string.format(
+
:attr('width', '1')
'mw-customtoggle-%s',
+
:wikitext(
tableIndex
 
)
 
)
 
:attr('style',
 
'text-align:left; line-height:1.40em; border-bottom:1pt solid dimgrey;'
 
)
 
:attr('colspan', '3' .. #extra_fields)
 
:wikitext(tbl_caption)
 
:done()
 
:done()
 
end
 
 
-- If the mod has no name then use the mod type:
 
local mod_name = rows['mods.name']
 
if mod_name == '' or mod_name == nil then
 
mod_name = rows['mods.mod_type']
 
end
 
 
-- Check if there are any extra properties to show in
 
-- the drop down list and then add a cell for that,
 
-- add this node at the end of the table row:
 
local td = mw.html.create('td')
 
if extra_fields ~= nil then
 
for _, extra_field in ipairs(extra_fields) do
 
td
 
:attr('width', '*')
 
:wikitext(string.format(
 
'%s:&nbsp;%s ',
 
extra_field,
 
rows[extra_field]
 
)
 
)
 
:done()
 
end
 
end
 
 
-- Add a table row with the interesting properties that
 
-- modifier has:
 
tbl
 
:tag('tr')
 
:attr('class', 'mw-collapsible mw-collapsed')
 
:attr(
 
'id',
 
 
string.format(
 
string.format(
'mw-customcollapsible-%s',
+
'%s&nbsp;%s',
tableIndex
+
m_game.level_requirement['short_upper']
  +
:gsub('%s', '&nbsp;'),
  +
rows['mods.required_level']
 
)
 
)
 
)
 
)
:tag('td')
+
:done()
:attr('width', '160')
+
:tag('td')
:wikitext(
+
:attr('width', '*')
string.format(
+
:wikitext(
'&nbsp;&nbsp;&nbsp;[[%s|%s]]',
+
string.format(
rows['mods._pageName'],
+
'%s%s',
mod_name:gsub('%s', '&nbsp;')
+
m_util.html.poe_color(
)
+
'mod',
  +
rows['mods.stat_text']
  +
:gsub('<br>', ', ')
  +
:gsub('<br />', ' ')
  +
) or '',
  +
mods_tags
 
)
 
)
:done()
+
)
:tag('td')
 
:attr('width', '1')
 
:wikitext(
 
string.format(
 
'%s&nbsp;%s',
 
m_game.level_requirement['short_upper']
 
:gsub('%s', '&nbsp;'),
 
rows['mods.required_level']
 
)
 
)
 
:done()
 
:tag('td')
 
:attr('width', '*')
 
:wikitext(
 
string.format(
 
'%s',
 
m_util.html.poe_color(
 
'mod',
 
rows['mods.stat_text']
 
:gsub('<br>', ', ')
 
:gsub('<br />', ' ')
 
)
 
)
 
)
 
:done()
 
:node(td)
 
 
:done()
 
:done()
  +
:node(td)
 
:done()
 
:done()
+
:done()
  +
-- Save the last mod group for later comparison:
 
last_group = rows['mods.mod_group']
+
-- Save the last mod group for later comparison:
last_type = rows['mods.mod_type']
+
last_group = rows['mods.mod_group']
end
+
last_type = rows['mods.mod_type']
 
end
 
end
 
 
out[#out+1] = tostring(container)
 
out[#out+1] = tostring(container)
 
end
 
end
  +
 
  +
-- Outro text:
  +
out[#out+1] = '<br>'
  +
out[#out+1] = m_util.html.poe_color(
  +
'normal',
  +
string.format('[[#Top|%s]]', i18n.drop_down_table.back_to_top)
  +
)
  +
 
return table.concat(out,'')
 
return table.concat(out,'')
 
end
 
end
Line 1,562: Line 1,949:
 
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,570: Line 1,957:
 
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

Revision as of 18:39, 7 December 2020

Template info icon Module documentation[view] [edit] [history] [purge]

Module implementing {{Modifier table}} with Cargo support.

--[[
Module responsible for displaying modifiers in various ways.

]]

local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local f_item_link = require('Module:Item link').item_link
local m_cargo = require('Module:Cargo')

local cargo = mw.ext.cargo

local p = {}

-- ----------------------------------------------------------------------------
-- Strings
-- ----------------------------------------------------------------------------
-- This section contains strings used by this module.
-- Add new strings here instead of in-code directly, this will help other
-- people to correct spelling mistakes easier and help with translation to
-- other PoE wikis.

local i18n = {
    mod_table = {
        name = m_util.html.abbr('Name', 'Name of the modifier if available or its internal identifier instead'),
        mod_group = m_util.html.abbr('Group', 'Only one modifier from the specified group can appear at a time under normal circumstances'),
        mod_type = 'Type',
        domain = '[[Modifiers#Mod_Domain|Domain]]',
        generation_type = '[[Modifiers#Mod_Generation_Type|Generation Type]]',
        required_level = '[[Image:Level_up_icon_small.png|link=|For generated item/monster modifiers the minimum item/monster level respectively. Some generation types may not require this condition to be met, however item level restrictions may be raised to 80% of this value.]]',
        labyrinth = '[[The Lord\'s Labyrinth|Labyrinth]]',
        stat_text = m_util.html.abbr('Stats', 'Stats of the modifier and the range they can roll in (if applicable)'),
        buff = m_util.html.abbr('Buff', 'ID of the buff granted and the values associated'),
        granted_skill = m_util.html.abbr('Skill', 'ID of the skill granted'),
        tags = '[[Modifiers#Tags|Tags]]',

        iiq = m_util.html.abbr('IIQ', 'increased Quantity of Items found in this Area'),
        iir = m_util.html.abbr('IIR', 'increased Rarity of Items found in this Area'),
        pack_size = m_util.html.abbr('Pack<br>Size', 'Monster pack size'),

        spawn_weights = m_util.html.abbr('Spawn Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
        generation_weights = m_util.html.abbr('Generation Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),

        normal_labyrinth = 'Normal Labyrinth',
        cruel_labyrinth = 'Cruel Labyrinth',
        merciless_labyrinth = 'Merciless Labyrinth',
        eternal_labyrinth = 'Eternal Labyrinth',
    },

    drop_down_table = {
        collapse_all = 'Collapse all',
        expand_all = 'Expand all',
        table_intro = 'The table below displays the available [[modifiers]] for [[item]]s such as',
        prefix = 'Prefix',
        suffix = 'Suffix',
        corrupted = 'Corrupted',
        enchant = 'Enchantment',
        elder_prefix = 'Elder prefix',
        elder_suffix = 'Elder suffix',
        shaper_prefix = 'Shaper prefix',
        shaper_suffix = 'Shaper suffix',
        delve_prefix = 'Delve prefix',
        delve_suffix = 'Delve suffix',
        crusader_prefix = 'Crusader prefix',
        crusader_suffix = 'Crusader suffix',
        eyrie_prefix = 'Redeemer prefix',
        eyrie_suffix = 'Redeemer suffix',
        basilisk_prefix = 'Hunter prefix',
        basilisk_suffix = 'Hunter suffix',
        adjudicator_prefix = 'Warlord prefix',
        adjudicator_suffix = 'Warlord suffix',
        veiled_prefix = 'Veiled prefix',
        veiled_suffix = 'Veiled suffix',
        mod_group = 'Mod group:',
        back_to_top = 'Back to top',
    },

    errors = {
        --
        -- Mod template
        --
        sell_price_duplicate_name = 'Do not specify a sell price item name multiple times. Adjust the amount instead.',
        sell_price_missing_argument = 'Both %s and %s must be specified',

        --
        -- Modifier link template
        --
        undefined_statid = 'Please define any of these stat ids: %s',
        incorrect_modid = 'Please change the name from "%s" to any of these modifier ids:<br>%s',
        multiple_results = 'Please choose only one of these modifier ids:<br>%s',
        no_results = 'No results found.',
    },
}


--
-- Helper/Utility functions
--

local h = {}

function h.header(str)
    --[[
    This function replaces specific numbers with a generic #.
    ]]

    local s = table.concat(m_util.string.split(str, '%(%d+%.*%d*%-%d+%.*%d*%)'), '#')
    s = table.concat(m_util.string.split(s, '%d+%.*%d*'), '#')
    s = table.concat(m_util.string.split(s, '<br>'), ', ')
    s = table.concat(m_util.string.split(s, '<br />'), ' ')

   return s
end

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 .. '.weight'
            },
            {
                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

function h.disambiguate_mod_name(results)
    --[[
        Disambiguates results from a mods query.
    ]]
    local str = {}
    for i,v in pairs(results) do
        str[#str+1] = string.format(
            '%s - %s ([[%s|page]])',
            v['mods.id'] or v['mods._pageName'] or '',
            string.gsub(
                v['mods.stat_text_raw'] or 'N/A',
                '<br>',
                ', '
            ) or '',
            v['mods._pageName'] or ''
        )
    end
    return table.concat(str, '<br>')
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, frame, 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, nil, value, opts)
            end
        end

        if #values == 0 then
            tr
                :wikitext(m_util.html.td.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

-- ----------------------------------------------------------------------------
-- Template: Mod table
-- ----------------------------------------------------------------------------

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, frame, 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, frame, tr, data, fields)
            local k = 'mods.domain'
            local i = tonumber(data[k])
            data[k] = m_game.constants.mod.domains[i]['short_upper']
            h.tbl.display.factory.value({})(tpl_args, frame, 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, frame, tr, data, fields)
            local k = 'mods.generation_type'
            local i = tonumber(data[k])
            data[k] = m_game.constants.mod.generation_types[i]['short_upper']
            h.tbl.display.factory.value{}(tpl_args, frame, tr, data, fields)
        end,
        order = 2001,
        sort_type = 'text',
    },
    {
        arg = {'group', 'mod_group'},
        header = i18n.mod_table.mod_group,
        fields = {'mods.mod_group'},
        display = h.tbl.display.factory.value{},
        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"
                    END
                )=labyrinth_text]],
            i18n.mod_table.normal_labyrinth,
            i18n.mod_table.cruel_labyrinth,
            i18n.mod_table.merciless_labyrinth,
            i18n.mod_table.eternal_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, frame, 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, frame, 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, frame, 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, frame, 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'}

function p.mod_table(frame)
    --[[
    Creates a generic table for modifiers.

    Examples
    --------
    = p.mod_table{
        q_tables='spawn_weights',
        q_join='mods._pageID=spawn_weights._pageID',
        q_where='mods.generation_type = 10 AND spawn_weights.tag = "boots" AND spawn_weights.weight > 0',
        q_orderBy='mods.id, mods.required_level',
        q_limit=100,
        stat_text=1,
        enchantment=1,
    }

    ]]


    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)

    -- 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(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, frame, tr, row, display_fields[i])
            else
                tr:wikitext(m_util.html.td.na())
            end
        end

        if tpl_args.type == 'map' then
            for stat_id, data in pairs(mod_table.stat_ids) do
                local stat_data = stats[row['mods._pageID']]
                if stat_data and stat_data[stat_id] then
                    local v = stat_data[stat_id]
                    local text
                    if v.min == v.max then
                        text = v.min
                    else
                        text = string.format('(%s to %s)', v.min, v.max)
                    end
                    tr
                        :tag('td')
                            :attr('data-sort-value', (v.min+v.max)/2)
                            :wikitext(string.format('%s%%', text))
                            :done()
                else
                    tr:wikitext(m_util.html.td.na())
                end
            end
        end

        for _, key in ipairs(mod_table.weights) do
            if tpl_args[key] then
                local weight_out = {}
                for _, wrow in ipairs(weights[key][row['mods._pageID']]) do
                    if wrow[key .. '.tag'] and wrow[key .. '.weight'] then
                        weight_out[#weight_out+1] = string.format(
                            '%s %s',
                            wrow[key .. '.tag'],
                            wrow[key .. '.weight']
                        )
                    end
                end

                if #weight_out > 0 then
                    tr
                        :tag('td')
                            :wikitext(table.concat(weight_out, '<br>'))
                            :done()
                else
                    tr:wikitext(m_util.html.td.na())
                end
            end
        end

        for _, field in ipairs(tpl_args._extra_fields) do
            if row[field] then
                tr
                    :tag('td')
                        :wikitext(row[field])
            else
                tr:wikitext(m_util.html.td.na())
            end
        end
    end

    return tostring(tbl)
end




-- ---------------------------------------------------------------------
-- Modifier link
-- ---------------------------------------------------------------------


function p.modifier_link(frame)
    --[[
    Finds and links to a modifier in formatted form.

    To do list
    ----------
    * Standardize hoverbox so it can be used in multiple places easily
    and make it easy to add rows of data.

    Examples
    --------
    = p.modifier_link{"Tyrannical"}
    = p.modifier_link{"Flaring"}
    = p.modifier_link{"Dictator's"}
    = p.modifier_link{"StrDexMaster%"}
    = p.modifier_link{"LocalIncreasedPhysicalDamagePercentAndAccuracyRating8", display='max', statid='local_physical_damage_+%'}

    ]]

    -- Get template args:
    local tpl_args = getArgs(frame, {parentFirst = true})
    local frame = m_util.misc.get_frame(frame)

    -- Aliases:
    tpl_args.modid = tpl_args.modid or tpl_args.id or tpl_args[1] or ''

    -- Query cargo rows:
    tpl_args.results = m_cargo.map_results_to_id{
        results=m_cargo.query(
            {'mods', 'mod_stats', 'spawn_weights'},
            {
                'mods.name',
                'mods.stat_text',
                'mods.stat_text_raw',
                'mods.generation_type',
                'mods.tags',
                'mods._pageName',
                'mod_stats.max',
                'mod_stats.min',
                'spawn_weights.tag',
                'spawn_weights.weight',
                'mods.id',
                'mod_stats.id'
            },
            {
                join = [[
                    mods._pageName=mod_stats._pageName,
                    mods._pageName=spawn_weights._pageName
                ]],
                where = string.format(
                    [[
                        (
                            mods.name LIKE "%s" or
                            mods.id LIKE "%s" or
                            mods.stat_text LIKE "%s" or
                            mods.stat_text_raw LIKE "%s"
                        )
                        AND mod_stats.id LIKE "%%%s%%"
                    ]],
                    tpl_args.modid,
                    tpl_args.modid,
                    tpl_args.modid,
                    tpl_args.modid,
                    tpl_args.statid or '%'
                ),
                -- groupBy = 'mods._pageID, mod_stats.id, spawn_weights.tag',
            }
        ),
        field='mods._pageName',
        keep_id_field=true,
        append_id_field=true,
    }

    -- Get a sorted list that only has unique page names:
    tpl_args.results_unique = {{}}
    for i, v in ipairs(tpl_args.results) do
        tpl_args.results_unique[i] = tpl_args.results[v][1]
    end

    -- Helpful error handling:
    local err_tbl = {
        {
            bool = #tpl_args.results == 0,
            disp = {
                i18n.errors.no_results,
            }
        },
        {
            bool = #tpl_args.results_unique > 1,
            disp = {
                i18n.errors.multiple_results,
                h.disambiguate_mod_name(tpl_args.results_unique),
            },
        },
        {
            bool = tpl_args.modid ~= tpl_args.results_unique[1]['mods.id'],
            disp = {
                string.gsub(
                    i18n.errors.incorrect_modid,
                    '%%s',
                    tpl_args.modid,
                    1
                ),
                h.disambiguate_mod_name(tpl_args.results_unique),
            },
        },
    }
    for _, v in ipairs(err_tbl) do
        if v.bool then
            local cats = {'Pages with modifier link errors'}
            return m_util.html.error(
                {msg = string.format(v.disp[1], v.disp[2]) .. m_util.misc.add_category(cats)}
            )
        end
    end

    -- Display formats:
    local display = {
        abbr = {
            display = function(tpl_args, frame)
                local name = m_util.html.poe_color(
                    'mod',
                    string.format(
                        '[[%s|%s]]',
                        tpl_args.results_unique[1]['mods._pageName'],
                        tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
                    )
                )

                local tooltip_table = {
                    m_util.html.poe_color(
                        'mod',
                        tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
                    ),
                    m_util.html.poe_color(
                        'help',
                        m_game.constants.mod.generation_types[
                            tonumber(
                                tpl_args.results_unique[1]['mods.generation_type']
                            )
                        ].full
                    ),
                    -- m_util.html.poe_color(
                        -- 'help',
                       -- table.concat(
                            -- m_util.string.split(tpl_args.results_unique[1]['mods.tags'] or '', ','),
                            -- ', '
                        -- )
                    -- ),
                    m_util.html.poe_color(
                        'normal',
                        tpl_args.results_unique[1]['mods.stat_text_raw']
                    ),
                }

                local tt_tbl_fltrd = {}
                for _,v in ipairs(tooltip_table) do
                    if v ~= nil and v ~= '' then
                        tt_tbl_fltrd[#tt_tbl_fltrd+1] = v
                    end
                end
                local tooltip = table.concat(tt_tbl_fltrd, '<br>')

                return m_util.html.tooltip(name, tooltip, class)
            end,
        },
        verbose = {
            display = function(tpl_args, frame)
                return string.format(
                    '%s - %s (%s)',
                    m_util.html.poe_color(
                        'mod',
                        string.format(
                            '[[%s|%s]]',
                            tpl_args.results_unique[1]['mods._pageName'],
                            tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
                        )
                    ),
                    m_util.html.poe_color(
                        'mod',
                        string.gsub(
                            tpl_args.results_unique[1]['mods.stat_text'],
                            '<br>',
                            ', '
                        )
                    ),
                    m_game.constants.mod.generation_types[
                        tonumber(
                            tpl_args.results_unique[1]['mods.generation_type']
                        )
                    ].full
                )
            end,
        },
        stat_text = {
            display = function(tpl_args, frame)
                return m_util.html.poe_color(
                    'mod',
                    tpl_args.results_unique[1]['mods.stat_text']
                )
            end,
        },
        max = {
            display = function(tpl_args, frame)
                local statid = {}
                for _, v in ipairs(tpl_args.results[tpl_args.results_unique[1]['mods._pageName']]) do
                    statid[#statid+1] = v['mod_stats.id']
                    if tpl_args.statid == v['mod_stats.id'] then
                        return v['mod_stats.max']
                    end
                end

                if tpl_args.statid == nil then
                    return m_util.html.error(
                        {
                            msg = string.format(
                                i18n.errors.undefined_statid,
                                table.concat(statid, ', ')
                            )
                        }
                    )
                end
            end
        },
        min = {
            display = function(tpl_args, frame)
                local statid = {}
                for _, v in ipairs(tpl_args.results[tpl_args.results_unique[1]['mods._pageName']]) do
                    statid[#statid+1] = v['mod_stats.id']
                    if tpl_args.statid == v['mod_stats.id'] then
                        return v['mod_stats.min']
                    end
                end

                if tpl_args.statid == nil then
                    return m_util.html.error(
                        {
                            msg = string.format(
                                i18n.errors.undefined_statid,
                                table.concat(statid, ', ')
                            )
                        }
                    )
                end
            end
        },
    }

    return display[tpl_args.display or 'abbr'].display(tpl_args, frame)
end




-- ---------------------------------------------------------------------
-- Drop down list
-- ---------------------------------------------------------------------

function h.get_spawn_chance(frame)
    --[[
    Calculates the spawn chance of a set of mods that all have a
    spawn weight.

    ]]

    -- Get template arguments:
    local tpl_args = getArgs(frame, {parentFirst=true})
    local frame = m_util.misc.get_frame(frame)

    -- Get the table:
    local tbl = tpl_args['tbl']

    -- Probabilities affecting the result besides the spawn weight:
    local chance_multiplier = tonumber(tpl_args['chance_multiplier']) or 1

    -- Total number of outcomes.
    local N = 0
    for i,_ in ipairs(tbl) do
        N = N + tbl[i]['spawn_weights.weight']
    end

    for i,_ in ipairs(tbl) do
        -- Number of ways it can happen:
        local n = tbl[i]['spawn_weights.weight']

        -- Truncated value:
        tbl[i]['spawn_weights.chance'] = string.format(
            "%0.2f%%",
            n/N * chance_multiplier*100
        )
    end

    return tbl
end

function p.drop_down_table(frame)
    --[[
    This function queries mods that can spawn on an item. It compares
    the item tags and the spawn weight tags. If there's a match and
    the spawn weight is larger than zero, then that mod is added to a
    drop down list.

    To Do:
    * Add support to:
        * Forsaken masters
        * Bestiary
    * Add a proper expand/collapse toggle for the entire header row so
        it reacts together with mw-collapsible.
    * Show Mod group in a better way perhaps:
        Mod group [Collapsible, default=Expanded]
            # to Damage [Collapsible, default=Collapsed]
                3 to Damage
                5 to Damage
    * Add a where condition that somehow filters out mods that obviously
      wont match with the item. spawn_weights.weight>0 isn't enough due
      to possible edge cases.


    Examples:
    Weapons
    = p.drop_down_table{item='Rusted Hatchet', header='One Handed Axes'}
    = p.drop_down_table{item='Stone Axe', header='Two Handed Axes'}

    Accessories
    = p.drop_down_table{item='Amber Amulet', header='Amulets'}

    Jewels
    = p.drop_down_table{item='Cobalt Jewel', header='Jewels'}

    Armour
    = p.drop_down_table{item='Plate Vest', header='Body armours'}

    Boots
    = p.drop_down_table{item='Iron Greaves', header='Boots'}

    = p.drop_down_table{
        item='Fishing Rod',
        header='FISH PLEASE',
        item_tags='fishing_rod',
        extra_fields='mods.tags'
    }

    = p.drop_down_table{
        item='Fishing Rod',
        item_tags='axe, one_hand_weapon, onehand, weapon, default',
        extra_item_tags='fishing_rod'
    }

    = p.drop_down_table{
        item='Vaal Blade',
    }

    ]]

    -- Get template arguments:
    local tpl_args = getArgs(frame, {parentFirst=true})
    local frame = m_util.misc.get_frame(frame)

    -- Format the cargo query:
    local where
    for _,v in ipairs({
            {tpl_args.page, 'items._pageName = "%s"'},
            {tpl_args.item, 'items.name = "%s"'},
    }) do
        if v[1] ~= nil then
            where = string.format(v[2], v[1])
            break
        end
    end

    local item_info = m_cargo.query(
        {'items'},
        {
            'items.name',
            'items.tags',
            'items.class_id',
            'items.inventory_icon',
            'items.html',
            'items.release_version',
            'items._pageName'
        },
        {
            where=where,
            groupBy='items._pageName',
            orderBy='items.name, items.release_version DESC',
        }
    )[1]

    -- Set the item class as key and the corresponding mod domain as
    -- value:
    local class_to_domain = {
        ['LifeFlask']=2,
        ['ManaFlask']=2,
        ['HybridFlask']=2,
        ['UtilityFlask']=2,
        ['UtilityFlaskCritical']=2,
        ['Map']=5,
        ['Jewel']=10,
        ['Leaguestone']=12,
        ['AbyssJewel']=13,
    }
    -- Get the domain, if it's not defined in the table assume it's
    -- in the item domain.
    item_info['items.domain'] = tonumber(tpl_args.domain) or class_to_domain[item_info['items.class_id']] or 1

    -- Convert the mod domain number to understandable text:
    item_info['items.domain_text'] = m_game.constants.mod.domains[item_info['items.domain']]['short_lower']

    -- Format item tags:
    tpl_args.item_tags = m_util.string.split_args(
        tpl_args.item_tags or item_info['items.tags'],
        {sep=',%s*'}
    )
    for _,v in ipairs(m_util.string.split_args(tpl_args.extra_item_tags, {sep=',%s*'})) do
        tpl_args.item_tags[#tpl_args.item_tags+1] = v
    end

    -- Format extra fields:
    local extra_fields = m_util.string.split_args(tpl_args.extra_fields, {sep=',%s*'})


    -- Get tags that are appended to special items:
    local elder_tag = m_game.constants.item.classes[item_info['items.class_id']]['elder_tag']
    local shaper_tag = m_game.constants.item.classes[item_info['items.class_id']]['shaper_tag']
    local crusader_tag = m_game.constants.item.classes[item_info['items.class_id']]['crusader_tag']
    local eyrie_tag = m_game.constants.item.classes[item_info['items.class_id']]['eyrie_tag']
    local basilisk_tag = m_game.constants.item.classes[item_info['items.class_id']]['basilisk_tag']
    local adjudicator_tag = m_game.constants.item.classes[item_info['items.class_id']]['adjudicator_tag']

    -- Create drop down lists in these sections and query in these
    -- generation types.
    local section = {
        {
            header = i18n.drop_down_table.prefix,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        1,
                        item_info['items.domain']
                    )
                end

                return table.concat(where, ' OR ')
            end,
        },
        {
            header = i18n.drop_down_table.suffix,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        2,
                        item_info['items.domain']
                    )
                end

                return table.concat(where, ' OR ')
            end,
        },
        {
            tags = {elder_tag},
            header = i18n.drop_down_table.elder_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    elder_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {elder_tag},
            header = i18n.drop_down_table.elder_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    elder_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {shaper_tag},
            header = i18n.drop_down_table.shaper_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    shaper_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {shaper_tag},
            header = i18n.drop_down_table.shaper_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    shaper_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            header = i18n.drop_down_table.delve_prefix,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        1,
                        16
                    )
                end

                return table.concat(where, ' OR ')
            end,
        },
        {
            header = i18n.drop_down_table.delve_suffix,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        2,
                        16
                    )
                end

                return table.concat(where, ' OR ')
            end,
        },
        {
            tags = {crusader_tag},
            header = i18n.drop_down_table.crusader_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    crusader_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {crusader_tag},
            header = i18n.drop_down_table.crusader_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    crusader_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {eyrie_tag},
            header = i18n.drop_down_table.eyrie_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    eyrie_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {eyrie_tag},
            header = i18n.drop_down_table.eyrie_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    eyrie_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {basilisk_tag},
            header = i18n.drop_down_table.basilisk_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    basilisk_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {basilisk_tag},
            header = i18n.drop_down_table.basilisk_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    basilisk_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {adjudicator_tag},
            header = i18n.drop_down_table.adjudicator_prefix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    adjudicator_tag,
                    1,
                    item_info['items.domain']
                )
            end,
        },
        {
            tags = {adjudicator_tag},
            header = i18n.drop_down_table.adjudicator_suffix,
            where = function(tpl_args, frame, value)
                return string.format(
                    [[
                        (spawn_weights.tag="%s"
                        AND mods.generation_type=%s
                        AND mods.domain=%s
                        AND mods.stat_text IS NOT NULL)
                    ]],
                    adjudicator_tag,
                    2,
                    item_info['items.domain']
                )
            end,
        },
        {
            header = i18n.drop_down_table.veiled_prefix,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL
                            AND mods.id LIKE "%%Master%%")
                        ]],
                        item_tag,
                        1,
                        9
                    )
                end

                return table.concat(where, ' OR ')
            end,
        },
        {
            header = i18n.drop_down_table.veiled_suffix,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL
                            AND mods.id LIKE "%%Master%%")
                        ]],
                        item_tag,
                        2,
                        9
                    )
                end

                return table.concat(where, ' OR ')
            end,
        },
        {
            header = i18n.drop_down_table.corrupted,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        5,
                        item_info['items.domain']
                    )
                end

                return table.concat(where, ' OR ')
            end,
            chance_multiplier = 1/4, -- See Vaal orb, for the 4 possible events.
            is_implicit = true,
        },
        {
            header = i18n.drop_down_table.enchant,
            where = function(tpl_args, frame, value)
                local where = {}
                for _, item_tag in ipairs(tpl_args.item_tags) do
                    where[#where+1] = string.format(
                        [[
                            (spawn_weights.tag="%s"
                            AND mods.generation_type=%s
                            AND mods.domain=%s
                            AND mods.stat_text IS NOT NULL)
                        ]],
                        item_tag,
                        10,
                        item_info['items.domain']
                    )
                end

                return table.concat(where, ' OR ')
            end,
            is_implicit = true,
        },
    }

    -- Save the original tag format:
    local item_tags_orig = {}
    for i,v in ipairs(tpl_args.item_tags) do
        item_tags_orig[i] = v
    end

    local item_mods = {}
    local mod_group_counter = {}
    mod_group_counter['all'] = {}
    local extra_fieldss = {}
    local table_index_base = -1
    for _, sctn in ipairs(section) do
        item_mods[sctn['header']] = {}

        -- Preallocate the mod group counter, implicit and explicit mods
        -- are counted separetely because they can spawn together:
        mod_group_counter[sctn['header']] = {}
        local adj = 'explicit'
        if sctn['is_implicit'] then
            adj = 'implicit'
        end
        for _, header in ipairs({sctn['header'], 'all'}) do
            if mod_group_counter[header][adj] == nil then
                mod_group_counter[header][adj] = {}
            end
        end

        local continue = true
        local current_tags
        if sctn['tags'] then
            -- some item classes do not have shaper/elder items, so the table
            -- will not contain any tags:
            if #sctn['tags'] == 0 then
                continue = false
            else
                current_tags = sctn['tags']
            end
        else
            current_tags = {}
            -- Reset to original tags:
            for i,v in ipairs(item_tags_orig) do
                current_tags[i] = v
            end
        end

        if continue then
            -- Cargo preparation:
            local q_fields = {
                'mods._pageName',
                'mods.name',
                'mods.id',
                'mods.required_level',
                'mods.generation_type',
                'mods.domain',
                'mods.mod_group',
                'mods.mod_type',
                'mods.stat_text',
                'mods.stat_text_raw',
                'mods.tags',
                'mod_stats.id',
                'spawn_weights.tag',
                'spawn_weights.weight',
                'spawn_weights.ordinal',
                'spawn_weights._pageName'
            }
            for i, v in ipairs(extra_fields) do
                q_fields[#q_fields+1] = v
            end

            -- Query mods and map the results to the pagename:
            local results = m_cargo.map_results_to_id{
                results=m_cargo.query(
                    {'mods', 'spawn_weights', 'mod_stats'},
                    q_fields,
                    {
                        join = [[
                            mods._pageName=spawn_weights._pageName,
                            mods._pageName=mod_stats._pageName
                        ]],
                        where = sctn['where'](tpl_args, frame, value),
                        groupBy = [[
                            mods._pageName,
                            spawn_weights.tag,
                            spawn_weights.weight
                        ]],
                        orderBy = [[
                            mods.generation_type,
                            mods.mod_group,
                            mods.mod_type,
                            mods.required_level,
                            mods._pageName,
                            spawn_weights.ordinal
                        ]],
                    }
                ),
                field='mods._pageName',
                keep_id_field=true,
                append_id_field=true,
            }

            if #results > 0 then
                -- Loop through all found modifiers:
                local last
                for _, id in ipairs(results) do
                    -- Loop through all the modifier tags until they match
                    -- the item tags:
                    local j = 0
                    local tag_match_stop
                    repeat
                        j = j+1
                        local mod_tag = results[id][j]['spawn_weights.tag']
                        local mod_tag_weight = tonumber(
                            results[id][j]['spawn_weights.weight']
                        )

                        -- Loop through the item tags until it matches the
                        -- spawn weight tag and the mod tag has a value larger than
                        -- zero:
                        local y = 0
                        local tag_match_add = false
                        repeat
                            y = y+1
                            tag_match_stop = ((mod_tag == current_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (results[id][j] == nil)
                            tag_match_add  =  (mod_tag == current_tags[y]) and ((mod_tag_weight or -1) > 0)
                        until tag_match_stop or y == #current_tags

                        -- If there's a match then save that mod and other
                        -- interesting information:
                        if tag_match_add then

                            -- Assume that the mod is global then go through
                            -- all the stat ids and check if any of the
                            -- stats are local:
                            local mod_scope = 'Global'
                            for _, vv in ipairs(results[id]) do
                                if vv['mod_stats.id']:find('.*local.*') ~= nil then
                                    mod_scope = 'Local'
                                end
                            end

                            -- Save the matching modifier tag:
                            local a = #item_mods[sctn['header']]
                            item_mods[sctn['header']][a+1] = results[id][j]

                            -- Save other interesting fields:
                            item_mods[sctn['header']][a+1]['mods.scope'] = mod_scope
                            item_mods[sctn['header']][a+1]['spawn_weight.idx_match'] = j
                            item_mods[sctn['header']][a+1]['mods.add'] = tag_match_add
                            item_mods[sctn['header']][a+1]['mods.stop'] = tag_match_stop

                            -- Count the mod groups:
                            local group = item_mods[sctn['header']][a+1]['mods.mod_group'] or 'nil_group'
                            for _, header in ipairs({sctn['header'], 'all'}) do
                                if mod_group_counter[header][adj][group] == nil then
                                    mod_group_counter[header][adj][group] = {}
                                end
                                local tp = results[id][j]['mods.mod_type']
                                local bef = mod_group_counter[header][adj][group][tp] or 0
                                mod_group_counter[header][adj][group][tp] = 1 + bef
                            end
                        end
                    until tag_match_stop
                end

                -- If the user wants to see the spawn chance then do the
                -- calculations and save that result as well:
                if tpl_args.spawn_chance ~= nil then
                    extra_fields[#extra_fields+1] = 'spawn_weights.chance'
                    item_mods[sctn['header']] = h.get_spawn_chance{
                        tbl = item_mods[sctn['header']],
                        chance_multiplier = sctn['chance_multiplier']
                    }
                end

                extra_fieldss[sctn['header']] = extra_fields
            end
        end
    end


    --
    -- Display the item mods
    --

    -- Introductory text:
    local out = {}
    out[#out+1] = string.format(
        '==%s== \n',
        tpl_args['header'] or table.concat(tpl_args.item_tags, ', ')
    )
    local expand_button = string.format(
        '<div style="float: right; text-align:center"><div class="mw-collapsible-collapse-all" style="cursor:pointer;">[%s]</div><hr><div class="mw-collapsible-expand-all" style="cursor:pointer;">[%s]</div></div>',
        i18n.drop_down_table.collapse_all,
        i18n.drop_down_table.expand_all
    )
    out[#out+1] = expand_button
    out[#out+1] = string.format('%s %s.<br><br><br>',
        i18n.drop_down_table.table_intro,
        f_item_link{
            page=item_info['items._pageName'],
            name=item_info['items.name'],
            inventory_icon=item_info['items.inventory_icon'] or '',
            html=item_info['items.html'] or '',
            skip_query=true
        }
    )

    -- Loop through the sections:
    for _, sctn in ipairs(section) do
        local extra_fields = extra_fieldss[sctn['header']]
        local adj = 'explicit'
        if sctn['is_implicit'] then
            adj = 'implicit'
        end

        -- Create html container:
        local container = mw.html.create('div')
            :attr('style', 'vertical-align:top; display:inline-block;')

        -- Create the drop down table with <table></table>:
        local headers = container
        if #item_mods[sctn['header']] > 0 then
            headers
                :tag('h3')
                    :wikitext(string.format('%s', sctn['header']))
                    :done()
                :done()
        end

        local total_mod_groups = 0
        for _ in pairs(mod_group_counter[sctn['header']][adj]) do
            total_mod_groups = 1+total_mod_groups
        end

        -- Loop through and add all matching mods to the <table>.
        local tbl, last_group, last_type
        for _, rows in ipairs(item_mods[sctn['header']]) do

            -- If the last mod group is different to the current
            -- mod group then assume the mod isn't related and start
            -- a new drop down list, if there's only one mod group
            -- then use mod type instead:
            if rows['mods.mod_group'] ~= last_group or (total_mod_groups == 1 and rows['mods.mod_type'] ~= last_type) then
                -- Check through all the mods and see if there are
                -- multiple mod types within the same mod group:
                local count = {}
                for _, n in ipairs(item_mods[sctn['header']]) do

                    -- If the mod has the same mod group, then add
                    -- the mod type to the counter. Only unique mod
                    -- types matter so the number is just a dummy
                    -- value:
                    if n['mods.mod_group'] == rows['mods.mod_group'] then
                        count[n['mods.mod_type']] = 1
                    end
                end

                -- Calculate how many unique mod types with the
                -- same mod group there are for all explicit or implicit
                -- sections since a mod with the same mod group can't
                -- spawn. Doesn't matter if it's prefix or suffix.
                local number_of_mod_types = 0
                for _ in pairs(mod_group_counter['all'][adj][rows['mods.mod_group']]) do
                    number_of_mod_types = 1 + number_of_mod_types
                end

                -- If there are multiple unique mod types with the
                -- same mod group then change the style of the drop
                -- down list to indicate it, if there's only one
                -- mod group in the generation type then ignore it:
                local table_index_mod_group
                if number_of_mod_types > 1 and total_mod_groups > 1 then
                    table_index_mod_group = table.concat(
                        {string.byte(rows['mods.mod_group'], 1, #rows['mods.mod_group'])},
                        ''
                    )

                    tbl_caption = string.format(
                        '%s',
                        m_util.html.poe_color(
                            'stat',
                            string.format(
                                '%s %s',
                                i18n.drop_down_table.mod_group,
                                rows['mods.mod_group']
                            )
                        ) or ''
                    )

                else
                    tbl_caption = string.format(
                        '%s (%s)',
                        m_util.html.poe_color(
                            'mod',
                            h.header(rows['mods.stat_text_raw'])
                        ) or '',
                        rows['mods.scope']
                    )
                end

                -- Create a table index for handling the collapsible:
                table_index_base = table_index_base+1
                if table_index_mod_group ~= nil then
                    table_index = table_index_mod_group
                else
                    table_index = table_index_base
                end

                -- Add class and style to the <table>:
                tbl = container:tag('table')
                tbl
                    :attr('class', 'mw-collapsible mw-collapsed')
                    :attr('style',
                        'text-align:left; line-height:1.60em; width:810px;'
                    )
                    :tag('th')
                        :attr('class',
                            string.format('mw-customtoggle-%s', table_index)
                        )
                        :attr('style',
                            'text-align:left; line-height:1.40em; border-bottom:1pt solid dimgrey;'
                        )
                        :attr('colspan', '3' .. #extra_fields)
                        :wikitext(tbl_caption)
                        :done()
                    :done()
            end

            -- If the mod has no name then use the mod type:
            local mod_name = rows['mods.name'] or ''
            if  mod_name == '' then
                mod_name = rows['mods.mod_type']
            end

            -- Check if there are any extra properties to show in
            -- the drop down list and then add a cell for that,
            -- add this node at the end of the table row:
            local td = mw.html.create('td')
            if extra_fields ~= nil then
                for _, extra_field in ipairs(extra_fields) do
                    td
                        :attr('width', '*')
                        :wikitext(string.format(
                            '%s:&nbsp;%s ',
                            extra_field,
                            rows[extra_field] or ''
                            )
                        )
                        :done()
                end
            end

            -- Style mods.tags:
            local mods_tags = table.concat(
                m_util.string.split_args(rows['mods.tags'], {sep=',%s*'}),
                ', '
            )
            if mods_tags ~= '' then
                mods_tags = m_util.html.tooltip('*', mods_tags, class)
            end

            -- Add a table row with the interesting properties that
            -- modifier has:
            tbl
                :tag('tr')
                    :attr('class', 'mw-collapsible mw-collapsed')
                    :attr(
                        'id',
                        string.format('mw-customcollapsible-%s', table_index)
                    )
                    :tag('td')
                        :attr('width', '160')
                        :wikitext(
                            string.format(
                                '&nbsp;&nbsp;&nbsp;[[%s|%s]]',
                                rows['mods._pageName'],
                                mod_name:gsub('%s', '&nbsp;')
                            )
                        )
                        :done()
                    :tag('td')
                        :attr('width', '1')
                        :wikitext(
                            string.format(
                                '%s&nbsp;%s',
                                m_game.level_requirement['short_upper']
                                    :gsub('%s', '&nbsp;'),
                                rows['mods.required_level']
                            )
                        )
                        :done()
                    :tag('td')
                        :attr('width', '*')
                        :wikitext(
                            string.format(
                                '%s%s',
                                m_util.html.poe_color(
                                    'mod',
                                    rows['mods.stat_text']
                                        :gsub('<br>', ', ')
                                        :gsub('<br />', ' ')
                                ) or '',
                                mods_tags
                            )
                        )
                        :done()
                    :node(td)
                    :done()
                :done()

            -- Save the last mod group for later comparison:
            last_group = rows['mods.mod_group']
            last_type = rows['mods.mod_type']
        end
        out[#out+1] = tostring(container)
    end

    -- Outro text:
    out[#out+1] = '<br>'
    out[#out+1] = m_util.html.poe_color(
        'normal',
        string.format('[[#Top|%s]]', i18n.drop_down_table.back_to_top)
    )

    return table.concat(out,'')
end

-- ----------------------------------------------------------------------------
-- Debug functions
-- ----------------------------------------------------------------------------

p.debug = {}
function p.debug.tbl_data(tbl)
    keys = {}
    for _, data in ipairs(mod_table.data) do
        if type(data.arg) == 'string' then
            keys[data.arg] = 1
        elseif type(data.arg) == 'table' then
            for _, arg in ipairs(data.arg) do
                keys[arg] = 1
            end
        end
    end

    for _, key in ipairs(mod_table.weights) do
        keys[key] = 1
    end

    local out = {}
    for key, _ in pairs(keys) do
        out[#out+1] = string.format("['%s'] = '1'", key)
    end

    return table.concat(out, ', ')
end

-- ----------------------------------------------------------------------------
--
-- ----------------------------------------------------------------------------

return p