Module:Modifier table: Difference between revisions
Jump to navigation
Jump to search
>Illviljan (Drop down mod lists: Separated the display part so it's possible to match mod groups in other sections, do implicit mods follow the mod group rules too? Changed color on mod groups and same mod groups in different sections are now linked to each other.) |
>Illviljan mNo edit summary |
||
Line 1,289: | Line 1,289: | ||
local table_index_base = -1 | local table_index_base = -1 | ||
for _, sctn in ipairs(section) do | for _, sctn in ipairs(section) do | ||
item_mods[sctn['header']] = {} | |||
mod_group_counter[sctn['header']] = {} | |||
local continue = true | local continue = true | ||
local current_tags | local current_tags | ||
Line 1,346: | Line 1,348: | ||
end | end | ||
if #results_unique > 0 then | if #results_unique > 0 then | ||
-- Loop through all found modifiers: | -- Loop through all found modifiers: | ||
local last | local last |
Revision as of 10:45, 19 January 2019
The above documentation is transcluded from Module:Modifier table/doc.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
--[[
Module 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 = '[[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',
mod_group = 'Mod group:',
},
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)
results = cargo.query(
string.format('mods,%s', table_name),
string.format('mods._pageID,%s.tag,%s.weight', table_name, table_name),
{
where=page_ids,
join=string.format('mods._pageID=%s._pageID', table_name),
orderBy=string.format('mods.id ASC,%s.ordinal ASC', table_name),
limit=5000,
}
)
if #results == 5000 then
error('Hit maximum cargo results')
end
return m_util.cargo.map_results_to_id{results=results,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
function h.cargo_query(tpl_args)
--[[
Returns a Cargo query of all the results.
tpl_args to be added to the cargo table needs to prefixed with q_.
* tpl_args.q_*
]]
-- Parse query arguments
local query = {
limit = 5000,
offset = 0,
}
for key, value in pairs(tpl_args) do
if string.sub(key, 0, 2) == 'q_' then
query[string.sub(key, 3)] = value
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
results[#results + 1] = v
end
until #result < query.limit
return results
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)
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)
local value = data['mods.domain']
tr
:tag('td')
:attr('data-sort-value', value)
:wikitext(m_game.constants.mod.domains[tonumber(value)]['short_upper'])
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)
local value = data['mods.generation_type']
tr
:tag('td')
:attr('data-sort-value', value)
:wikitext(m_game.constants.mod.generation_types[tonumber(value)]['short_upper'])
end,
order = 2001,
sort_type = 'text',
},
{
arg = {'group', 'mod_group'},
header = i18n.mod_table.mod_group,
fields = {'mods.mod_group'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(data['mods.mod_group'])
end,
order = 2002,
sort_type = 'text',
},
{
arg = {'mod_type'},
header = i18n.mod_table.mod_type,
fields = {'mods.mod_type'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(data['mods.mod_type'])
end,
order = 2003,
sort_type = 'text',
},
{
arg = {'level', 'required_level'},
header = i18n.mod_table.required_level,
fields = {'mods.required_level'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(data['mods.required_level'])
end,
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 = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(data['labyrinth_text'])
end,
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)
local text
-- 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
text = table.concat(out, '<br>')
else
text = data['mods.stat_text']
end
tr
:tag('td')
:wikitext(text)
end,
order = 3000,
sort_type = 'text',
},
{
arg = 'buff',
header = i18n.mod_table.buff,
fields = {'mods.granted_buff_id', 'mods.granted_buff_value'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(string.format('%s %s', data['mods.granted_buff_id'], data['mods.granted_buff_value']))
end,
order = 4000,
sort_type = 'text',
},
{
arg = {'skill', 'granted_skill'},
header = i18n.mod_table.granted_skill,
fields = {'mods.granted_skill'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(data['mods.granted_skill'])
end,
order = 4001,
sort_type = 'text',
},
{
arg = {'tags'},
header = i18n.mod_table.tags,
fields = {'mods.tags'},
display = function(tpl_args, frame, tr, data)
tr
:tag('td')
:wikitext(table.concat(m_util.string.split(data['mods.tags'], ','), ', '))
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 tables
local tables = 'mods'
if tpl_args.q_tables then
tables = tables .. ',' .. tpl_args.q_tables
end
-- Set required fields
local fields = {
'mods._pageID',
}
for _, rowinfo in ipairs(row_infos) do
if type(rowinfo.fields) == 'function' then
rowinfo.fields = rowinfo.fields()
end
for index, field in ipairs(rowinfo.fields) do
rowinfo.options[index] = rowinfo.options[index] or {}
fields[#fields+1] = field
end
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
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 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
local stat_results = 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',
limit=5000,
}
)
if #stat_results == 5000 then
error('Hit maximum cargo results')
end
stats = m_util.cargo.map_results_to_id{results=stat_results, 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
if tpl_args.q_fields then
tpl_args._extra_fields = m_util.string.split(tpl_args.q_fields, ',')
for index, field in ipairs(tpl_args._extra_fields) do
field = m_util.string.split(field, '=')
-- 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
local tbl = mw.html.create('table')
tbl:attr('class', 'wikitable sortable modifier-table')
-- Header
local tr = tbl:tag('tr')
for _, row_info in ipairs(row_infos) do
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 _, rowinfo in ipairs(row_infos) do
-- this has been cast from a function in an earlier step
local display = true
for index, field in ipairs(rowinfo.fields) do
-- this will bet set to an empty value not nil confusingly
if row[field] == '' then
if rowinfo.options[index].optional ~= true then
display = false
break
else
row[field] = nil
end
end
end
if display then
rowinfo.display(tpl_args, frame, tr, row, rowinfo.fields)
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
weight_out[#weight_out+1] = string.format('%s %s', wrow[key .. '.tag'], wrow[key .. '.weight'])
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 ''
-- Define query arguments:
local tables = {'mods', 'mod_stats', 'spawn_weights'}
local fields = {'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'}
local query = {
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',
}
-- Query cargo rows:
local results = m_util.cargo.query(tables, fields, query, args)
-- 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
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
-- If no results are found then just create a empty list to avoid
-- index errors.
if tpl_args.results_unique[1] == nil then
tpl_args.results_unique[1] = {}
end
-- Helpful error handling:
local err_tbl = {
{
bool = #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.tbl.mod_stats[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.tbl.mod_stats[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', '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 = {
['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,
}
-- Get the domain, if it's not defined in the table assume it's
-- in the item domain.
item_info['items.domain'] = class_to_domain[item_info['items.class']] 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']
-- For some reason cargo queried item tags, are not comma-space
-- separated any more.
if tpl_args.item_tags ~= nil then
tpl_args.item_tags = m_util.string.split(tpl_args.item_tags, ', ')
else
tpl_args.item_tags = m_util.string.split(item_info['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
-- Create drop down lists in these sections and query in these
-- generation types.
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 section = {}
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,
},
{
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,
},
}
-- Introductory text:
local out = {}
out[#out+1] = string.format(
'==%s== \n',
tpl_args['header'] or table.concat(tpl_args.item_tags, ', ')
)
out[#out+1] = 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] = 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
}
)
-- 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']] = {}
mod_group_counter[sctn['header']] = {}
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:
tpl_args.q_tables = 'mods, spawn_weights, mod_stats'
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.stat_text_raw, mods.tags, mods._pageName, mod_stats.id, spawn_weights.tag, spawn_weights.weight, spawn_weights.ordinal, spawn_weights._pageName'
tpl_args.q_join = 'mods._pageName=spawn_weights._pageName, mods._pageName=mod_stats._pageName'
tpl_args.q_where = sctn['where'](tpl_args, frame, value)
tpl_args.q_groupBy = 'mods._pageName, spawn_weights.tag, spawn_weights.weight'
tpl_args.q_orderBy = 'mods.generation_type, mods.mod_group, mods.mod_type, mods.required_level, mods._pageName, spawn_weights.ordinal'
local extra_fields = {}
if tpl_args.extra_fields ~= nil then
extra_fields = m_util.string.split(tpl_args.extra_fields, ', ')
tpl_args.q_fields = string.format(
'%s, %s',
tpl_args.q_fields,
table.concat(extra_fields, ', ')
)
end
-- Query mods:
results = h.cargo_query(tpl_args)
-- 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
if #results_unique > 0 then
-- Loop through all found modifiers:
local last
for _, v in ipairs(results_unique) do
local pagename = v['spawn_weights._pageName']
if sctn['header'] == i18n.drop_down_table.elder_prefix then
-- error(mw.dumpObject(spawn_weights))
end
-- 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
-- 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 (spawn_weights[pagename][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(spawn_weights[pagename]) 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] = 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'
for _,v in ipairs({sctn['header'], 'all'}) do
if mod_group_counter[v][group] == nil then
mod_group_counter[v][group] = {}
end
local tp = spawn_weights[pagename][j]['mods.mod_type']
local bef = mod_group_counter[v][group][tp] or 0
mod_group_counter[v][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
--
for _, sctn in ipairs(section) do
local extra_fields = extra_fieldss[sctn['header']]
-- 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']]) 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 sections since
-- a mod with the same mod group can't spawn. Doesn't
-- matter if it's prefix or suffix. Does it matter for
-- implicit vs explicit though?
local number_of_mod_types = 0
for _ in pairs(mod_group_counter['all'][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']
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: %s ',
extra_field,
rows[extra_field]
)
)
:done()
end
end
-- Style mods.tags:
local mods_tags = table.concat(m_util.string.split(rows['mods.tags'], ','), ', ')
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(
' [[%s|%s]]',
rows['mods._pageName'],
mod_name:gsub('%s', ' ')
)
)
:done()
:tag('td')
:attr('width', '1')
:wikitext(
string.format(
'%s %s',
m_game.level_requirement['short_upper']
:gsub('%s', ' '),
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
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