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
No edit summary
(Show stats in the data pages.)
 
(39 intermediate revisions by 2 users not shown)
Line 4: Line 4:
   
 
local m_util = require('Module:Util')
 
local m_util = require('Module:Util')
  +
local m_cargo = require('Module:Cargo')
 
local getArgs = require('Module:Arguments').getArgs
 
local getArgs = require('Module:Arguments').getArgs
 
local f_infocard = require('Module:Infocard')._main
 
local f_infocard = require('Module:Infocard')._main
Line 18: Line 19:
 
cats = {
 
cats = {
 
data = 'Passive skill data',
 
data = 'Passive skill data',
  +
  +
keystone = 'Keystone passive skills',
  +
notable = 'Notable passive skills',
  +
basic = 'Basic passive skills',
  +
ascendancy_notable = 'Ascendancy notable passive skills',
  +
ascendancy_basic = 'Ascendancy basic passive skills',
 
},
 
},
 
 
 
passive_box = {
 
passive_box = {
ascendancy = 'Ascendancy',
 
 
keystone = 'Keystone',
 
keystone = 'Keystone',
 
notable = 'Notable Passive Skill',
 
notable = 'Notable Passive Skill',
passive = 'Passive Skill',
+
basic = 'Passive Skill',
  +
ascendancy_notable = 'Ascendancy Notable Passive Skill',
  +
ascendancy_basic = 'Ascendancy Passive Skill',
  +
},
  +
  +
intro = {
  +
text_with_name = "'''%s''' is the internal id for the [[%s|%s]] [[passive skill]]. ",
  +
text_without_name = "'''%s''' is the internal id of an unnamed [[passive skill]]. ",
  +
},
  +
  +
passive_box_table = {
  +
id = 'Id',
  +
int_id = 'Integer Id',
  +
flavour_text = 'Flavour Text',
  +
reminder_text = 'Reminder Text',
  +
skill_points = 'Skill Points Granted',
  +
ascendancy_class = 'Ascendancy Class',
  +
connections = 'Connections',
 
},
 
},
 
 
Line 46: Line 69:
 
tables.passive_skills = {
 
tables.passive_skills = {
 
table = 'passive_skills',
 
table = 'passive_skills',
order = {'id', 'int_id', 'name', 'flavour_text', 'reminder_text', 'buff_id', 'skill_points', 'icon', 'ascendancy_class', 'is_keystone', 'is_notable', 'is_multiple_choice_option', 'is_multiple_choice', 'is_icon_only', 'is_jewel_socket', 'is_ascendancy_starting_node', 'stat_text', 'stat_text_raw', 'connections',},
+
order = {'id', 'int_id', 'name', 'main_page', 'flavour_text', 'reminder_text', 'buff_id', 'skill_points', 'icon', 'ascendancy_class', 'is_keystone', 'is_notable', 'is_multiple_choice_option', 'is_multiple_choice', 'is_icon_only', 'is_jewel_socket', 'is_ascendancy_starting_node', 'stat_text', 'stat_text_raw', 'connections',},
 
fields = {
 
fields = {
 
id = {
 
id = {
 
field = 'id',
 
field = 'id',
type = 'String(unique)',
+
type = 'String',
 
required = true,
 
required = true,
 
},
 
},
 
int_id = {
 
int_id = {
 
field = 'int_id',
 
field = 'int_id',
type = 'Integer(unique)',
+
type = 'Integer',
 
required = true,
 
required = true,
 
},
 
},
Line 61: Line 84:
 
field = 'name',
 
field = 'name',
 
type = 'String',
 
type = 'String',
  +
},
  +
main_page = {
  +
field = 'main_page',
  +
type = 'Page',
 
},
 
},
 
flavour_text = {
 
flavour_text = {
Line 67: Line 94:
 
},
 
},
 
reminder_text = {
 
reminder_text = {
field = 'id',
+
field = 'reminder_text',
 
type = 'Text',
 
type = 'Text',
 
},
 
},
 
buff_id = {
 
buff_id = {
field = 'id',
+
field = 'buff_id',
 
type = 'String',
 
type = 'String',
 
},
 
},
Line 169: Line 196:
 
},
 
},
 
}
 
}
  +
}
  +
  +
local display = {}
  +
display.map_to_property = {'icon', 'is_keystone', 'is_notable', 'ascendancy_class'}
  +
display.tbl = {
  +
{
  +
key = 'id',
  +
header = i18n.passive_box_table.id,
  +
display = nil,
  +
},
  +
{
  +
key = 'int_id',
  +
header = i18n.passive_box_table.int_id,
  +
display = nil,
  +
},
  +
{
  +
css = 'tc -flavour',
  +
key = 'flavour_text',
  +
header = i18n.passive_box_table.flavour_text,
  +
display = nil,
  +
},
  +
{
  +
key = 'reminder_text',
  +
header = i18n.passive_box_table.reminder_text,
  +
display = nil,
  +
},
  +
{
  +
key = 'skill_points',
  +
header = i18n.passive_box_table.skill_points,
  +
display = nil,
  +
},
  +
{
  +
key = 'ascendancy_class',
  +
header = i18n.passive_box_table.ascendancy_class,
  +
display = function (tpl_args, frame, value)
  +
return string.format('[[%s]]', value)
  +
end,
  +
},
  +
{
  +
key = 'connections',
  +
header = i18n.passive_box_table.connections,
  +
display = function (tpl_args, frame, value)
  +
local results = m_cargo.map_results_to_id{
  +
field='passive_skills.id',
  +
results=m_cargo.array_query{
  +
tables={'passive_skills'},
  +
fields={'passive_skills.name', 'passive_skills._pageName'},
  +
id_array=value,
  +
id_field='passive_skills.id',
  +
ignore_missing=true,
  +
}
  +
}
  +
  +
local ul = mw.html.create('ul')
  +
for _, key in ipairs(value) do
  +
local row = results[key]
  +
if row then
  +
row = row[1]
  +
end
  +
local text
  +
if row then
  +
text = string.format('[[%s|%s]]', row['passive_skills._pageName'], row['passive_skills.name'] or row['passive_skills._pageName'])
  +
else
  +
text = key
  +
end
  +
ul
  +
:tag('li')
  +
:wikitext(text)
  +
:done()
  +
end
  +
  +
return tostring(ul)
  +
end,
  +
},
 
}
 
}
   
Line 177: Line 278:
 
local h = {}
 
local h = {}
   
function h.format_passive_icon(passive)
+
function h.format_passive_icon(passive, passive_type)
 
if passive['passive_skills.icon'] == nil then
 
if passive['passive_skills.icon'] == nil then
 
return ''
 
return ''
 
end
 
end
 
 
  +
local cls = string.format('passive-icon-type__%s', passive_type)
local cls
 
if passive['passive_skills.is_keystone'] == 1 then
+
local main_page = passive['passive_skills.main_page'] or passive['main_pages._pageName'] or passive['passive_skills.name'] or passive['passive_skills.icon']
cls = 'keystone'
 
elseif passive['passive_skills.is_notable'] == 1 then
 
cls = 'notable'
 
else
 
cls = 'basic'
 
end
 
 
if passive['passive_skills.ascendancy_class'] ~= nil then
 
cls = 'ascendancy_' .. cls
 
end
 
cls = string.format('passive-icon-type__%s', cls)
 
 
 
div = mw.html.create('div')
 
div = mw.html.create('div')
 
div:addClass('passive-icon-container')
 
div:addClass('passive-icon-container')
Line 202: Line 291:
 
:addClass('passive-icon-frame')
 
:addClass('passive-icon-frame')
 
:done()
 
:done()
div:wikitext(string.format('[[%s]]', passive['passive_skills.icon']))
+
div:wikitext(
  +
string.format(
  +
'[[%s|link=%s]]',
  +
passive['passive_skills.icon'],
  +
main_page
  +
)
  +
)
 
 
 
return tostring(div)
 
return tostring(div)
Line 238: Line 333:
 
return table.concat(out, '<hr>')
 
return table.concat(out, '<hr>')
 
end
 
end
  +
  +
h.type_order = {'basic', 'notable', 'keystone', 'ascendancy_basic', 'ascendancy_notable'}
  +
function h.get_type(passive)
  +
local key
  +
if tonumber(passive['passive_skills.is_keystone']) == 1 then
  +
key = 'keystone'
  +
elseif tonumber(passive['passive_skills.is_notable']) == 1 then
  +
key = 'notable'
  +
else
  +
key = 'basic'
  +
end
  +
  +
if passive['passive_skills.ascendancy_class'] ~= nil then
  +
key = 'ascendancy_' .. key
  +
end
  +
  +
return key
  +
end
  +
  +
function h.sort_by_type(results)
  +
local new = {}
  +
for _, key in ipairs(h.type_order) do
  +
new[key] = {}
  +
end
  +
  +
for _, passive in ipairs(results) do
  +
table.insert(new[h.get_type(passive)], passive)
  +
end
  +
  +
return new
  +
end
  +
  +
function h.intro_text(tpl_args, frame)
  +
--[[
  +
Display an introductory text about the passive skill.
  +
]]
  +
local out = {}
  +
if mw.ustring.find(tpl_args['id'], '_') then
  +
out[#out+1] = frame:expandTemplate{
  +
title='Incorrect title',
  +
args = {title=tpl_args['id']}
  +
}
  +
end
  +
  +
if tpl_args['name'] then
  +
out[#out+1] = string.format(
  +
i18n.intro.text_with_name,
  +
tpl_args['id'],
  +
tpl_args['main_page'] or tostring(mw.title.getCurrentTitle()),
  +
tpl_args['name']
  +
)
  +
else
  +
out[#out+1] = string.format(
  +
i18n.intro.text_without_name,
  +
tpl_args['id']
  +
)
  +
end
  +
  +
return table.concat(out)
  +
end
  +
  +
function h.stat_box(tpl_args, frame)
  +
--[[
  +
Display the stat box.
  +
]]
  +
local container = mw.html.create('div')
  +
container
  +
:attr('class', 'modbox floatright')
  +
  +
-- stat table
  +
tbl = container:tag('table')
  +
tbl
  +
:attr('class', 'wikitable sortable')
  +
-- :attr('style', 'style="width: 100%;"')
  +
:tag('tr')
  +
:tag('th')
  +
:attr('colspan', 3)
  +
:wikitext('Stats')
  +
:done()
  +
:done()
  +
:tag('tr')
  +
:tag('th')
  +
:wikitext('#')
  +
:done()
  +
:tag('th')
  +
:wikitext('Stat Id')
  +
:done()
  +
:tag('th')
  +
:wikitext('Value')
  +
:done()
  +
:done()
  +
:done()
  +
:done()
  +
  +
local i = 0
  +
local value = nil
  +
repeat
  +
i = i + 1
  +
value = {
  +
id = tpl_args[string.format('stat%s_id', i)],
  +
value = tpl_args[string.format('stat%s_value', i)],
  +
}
  +
  +
if value.id then
  +
tbl
  +
:tag('tr')
  +
:tag('td')
  +
:wikitext(i)
  +
:done()
  +
:tag('td')
  +
:wikitext(value.id)
  +
:done()
  +
:tag('td')
  +
:wikitext(value.value)
  +
:done()
  +
:done()
  +
:done()
  +
end
  +
until value.id == nil
  +
  +
return tostring(container)
  +
end
  +
   
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
Line 245: Line 463:
 
local p = {}
 
local p = {}
   
  +
-- This way the helper functions can be used in other modules
p.table_passive_skills = m_util.cargo.declare_factory{data=tables.passive_skills}
 
  +
p.h = h
p.table_passive_skill_stats = m_util.cargo.declare_factory{data=tables.passive_skill_stats}
 
  +
  +
-- Declare cargo tables:
  +
p.table_passive_skills = m_cargo.declare_factory{data=tables.passive_skills}
  +
p.table_passive_skill_stats = m_cargo.declare_factory{data=tables.passive_skill_stats}
   
 
function p.passive_skill(frame)
 
function p.passive_skill(frame)
  +
--[[
  +
Stores data and displays a infobox about the passive skill.
  +
  +
Examples
  +
--------
  +
= p.passive_skill{
  +
id = 'life_life_leech1629',
  +
int_id = '27788',
  +
name = 'Blood Drinker',
  +
is_notable = 'True',
  +
icon = 'lifeleech',
  +
stat1_id = 'maximum_life_+%',
  +
stat1_value = '8',
  +
stat2_id = 'base_life_leech_from_attack_damage_permyriad',
  +
stat2_value = '40',
  +
stat_text = '8% increased maximum life<br>0.4% of Attack Damage Leeched as Life',
  +
connections = 'life1415,life1413',
  +
}
  +
  +
]]
  +
 
-- Get args
 
-- Get args
 
tpl_args = getArgs(frame, {
 
tpl_args = getArgs(frame, {
Line 265: Line 508:
 
for _, stat in ipairs(tpl_args.stats) do
 
for _, stat in ipairs(tpl_args.stats) do
 
stat._table = tables.passive_skill_stats.table
 
stat._table = tables.passive_skill_stats.table
m_util.cargo.store(frame, stat)
+
m_cargo.store(frame, stat)
 
end
 
end
 
 
cats = {
+
--
  +
-- Infobox
i18n.cats.data,
 
  +
--
  +
local passive = {}
  +
for _, key in ipairs(display.map_to_property) do
  +
local v = tpl_args[key]
  +
if type(v) == 'boolean' then
  +
if v then
  +
v = 1
  +
else
  +
v = 0
  +
end
  +
end
  +
passive[string.format('%s.%s', tables.passive_skills.table, tables.passive_skills.fields[key].field)] = v
  +
end
  +
  +
local type_key = h.get_type(passive)
  +
  +
local infocard_args = {}
  +
infocard_args.header = tpl_args.name
  +
infocard_args.subheader = i18n.passive_box[type_key]
  +
  +
local tbl = mw.html.create('table')
  +
for _, data in ipairs(display.tbl) do
  +
local value = tpl_args[data.key]
  +
-- if default is nil, this will be compared against nil which is what we want, so value ~= nil isn't needed
  +
if value ~= tables.passive_skills.fields[data.key].default then
  +
local dsp
  +
if data.display then
  +
dsp = data.display(tpl_args, frame, value)
  +
else
  +
dsp = value
  +
end
  +
tbl
  +
:tag('tr')
  +
:tag('th')
  +
:wikitext(data.header)
  +
:done()
  +
:tag('td')
  +
:attr('class', data.css)
  +
:wikitext(dsp)
  +
:done()
  +
:done()
  +
end
  +
end
  +
  +
infocard_args[1] = tostring(tbl)
  +
infocard_args[2] = tpl_args.stat_text
  +
infocard_args[3] = h.format_passive_icon(passive, type_key)
  +
  +
local out = {
  +
f_infocard(infocard_args),
  +
h.intro_text(tpl_args, frame),
  +
h.stat_box(tpl_args, frame),
 
}
 
}
 
 
return m_util.misc.add_category(cats)
+
local cats = {
  +
i18n.cats.data,
  +
}
  +
return table.concat(out) .. m_util.misc.add_category(cats)
 
end
 
end
   
 
function p.passive_skill_box(frame)
 
function p.passive_skill_box(frame)
  +
--[[
  +
Queries a passive skill and displays it.
  +
  +
Examples
  +
--------
  +
= p.passive_skill_box{name='Ghost Reaver'}
  +
  +
]]
  +
 
-- Get args
 
-- Get args
 
tpl_args = getArgs(frame, {
 
tpl_args = getArgs(frame, {
Line 285: Line 592:
 
 
 
if not tpl_args.q_where and tpl_args.name then
 
if not tpl_args.q_where and tpl_args.name then
tpl_args.q_where = string.format('passive_skills.name="%s"', tpl_args.name)
+
tpl_args.q_where = string.format('passive_skills.name="%s" AND passive_skills.stat_text IS NOT NULL', tpl_args.name)
 
elseif not (tpl_args.q_where and not tpl_args.name) then
 
elseif not (tpl_args.q_where and not tpl_args.name) then
 
error('q_where or name must be specified')
 
error('q_where or name must be specified')
 
end
 
end
 
 
  +
local results = m_cargo.query(
tpl_args.q_where = tpl_args.q_where .. ' AND passive_skills.stat_text IS NOT NULL'
 
 
local results = m_util.cargo.query(
 
 
{'passive_skills'},
 
{'passive_skills'},
 
{
 
{
 
'passive_skills._pageName',
 
'passive_skills._pageName',
  +
'passive_skills.main_page',
  +
'passive_skills.name',
 
'passive_skills.stat_text',
 
'passive_skills.stat_text',
 
-- TODO: only really need these once, maybe put in extra query
 
-- TODO: only really need these once, maybe put in extra query
 
'passive_skills.flavour_text',
 
'passive_skills.flavour_text',
'passive_skills.stat_text',
 
 
'passive_skills.icon',
 
'passive_skills.icon',
 
'passive_skills.is_keystone',
 
'passive_skills.is_keystone',
 
'passive_skills.is_notable',
 
'passive_skills.is_notable',
 
'passive_skills.ascendancy_class',
 
'passive_skills.ascendancy_class',
  +
'passive_skills.id'
 
},
 
},
 
{
 
{
 
where=tpl_args.q_where,
 
where=tpl_args.q_where,
 
orderBy='passive_skills.stat_text',
 
orderBy='passive_skills.stat_text',
  +
limit=5000,
 
}
 
}
 
)
 
)
 
 
if #results == 0 then
 
if #results == 0 then
 
error(i18n.errors.no_passives_found)
 
error(i18n.errors.no_passives_found)
 
end
 
end
 
 
local stats, stat_order = h.make_stat_order(results)
+
results = h.sort_by_type(results)
  +
local out = {}
  +
local cats = {}
  +
for _, type_key in ipairs(h.type_order) do
  +
local type_results = results[type_key]
  +
if #type_results > 0 then
  +
cats[#cats+1] = i18n.cats[type_key]
  +
local stats, stat_order = h.make_stat_order(type_results)
  +
  +
local passive = type_results[1]
  +
  +
local infocard_args = {}
  +
infocard_args.header = passive['passive_skills.name']
  +
infocard_args.subheader = i18n.passive_box[type_key]
  +
  +
infocard_args[1] = h.format_passive_icon(passive, type_key)
  +
infocard_args[2] = h.stat_page_links(stat_order, stats)
  +
infocard_args[3] = m_util.html.poe_color('flavour', passive['passive_skills.flavour_text'])
  +
  +
out[#out+1] = f_infocard(infocard_args)
  +
  +
-- Store as main page:
  +
for _, v in ipairs(type_results) do
  +
m_cargo.store(
  +
frame,
  +
{
  +
_table='main_pages',
  +
id=v['passive_skills.id'],
  +
}
  +
)
  +
end
  +
end
  +
end
 
 
  +
if tpl_args.cats == nil or m_util.cast.boolean(tpl_args.cats) then
local passive = results[1]
 
  +
out[#out+1] = m_util.misc.add_category(cats)
  +
end
 
 
  +
return table.concat(out)
local infocard_args = {}
 
  +
end
infocard_args.header = tpl_args.name
 
  +
if passive['passive_skills.is_keystone'] == 1 then
 
  +
function p.passive_skill_link(frame)
infocard_args.subheader = i18n.passive_box.keystone
 
  +
--[[
elseif passive['passive_skills.is_notable'] == 1 then
 
  +
Links a passive skill
infocard_args.subheader = i18n.passive_box.notable
 
  +
  +
Examples
  +
--------
  +
= p.passive_skill_link{id='AscendancyAscendant45', format='tablerow'}
  +
]]
  +
  +
-- Get args
  +
tpl_args = getArgs(frame, {
  +
parentFirst = true
  +
})
  +
frame = m_util.misc.get_frame(frame)
  +
  +
tpl_args.name = tpl_args.name or tpl_args[1]
  +
if tpl_args.name then
  +
tpl_args.q_where = string.format('passive_skills.name="%s"', tpl_args.name)
  +
elseif tpl_args.id then
  +
tpl_args.q_where = string.format('passive_skills.id="%s"', tpl_args.id)
  +
elseif tpl_args.q_where then
 
else
 
else
  +
error('Either name, id or q_where must be specified')
infocard_args.subheader = i18n.passive_box.passive
 
 
end
 
end
 
 
  +
local results = m_cargo.query(
if passive['passive_skills.ascendancy_class'] ~= nil then
 
  +
{'passive_skills', 'main_pages'},
infocard_args.subheader = i18n.passive_box.ascendancy .. ' ' .. infocard_args.subheader
 
  +
{
  +
'passive_skills._pageName',
  +
'passive_skills.stat_text',
  +
'passive_skills.main_page',
  +
'passive_skills.name',
  +
'passive_skills.icon',
  +
'passive_skills.is_keystone',
  +
'passive_skills.is_notable',
  +
'passive_skills.ascendancy_class',
  +
'main_pages._pageName',
  +
},
  +
{
  +
join='passive_skills.id=main_pages.id',
  +
where=string.format('(%s) AND passive_skills.stat_text IS NOT NULL', tpl_args.q_where),
  +
orderBy='passive_skills.stat_text',
  +
limit=2,
  +
}
  +
)
  +
  +
if #results > 1 then
  +
error('Too many passives found!')
  +
elseif #results < 1 then
  +
error('No passives found')
 
end
 
end
  +
local passive = results[1]
 
 
  +
if tpl_args.format == 'tablerow' then
infocard_args[1] = h.format_passive_icon(passive)
 
  +
local main_page = passive['passive_skills.main_page'] or passive['main_pages._pageName'] or passive['passive_skills.name']
infocard_args[2] = h.stat_page_links(stat_order, stats)
 
  +
return string.format(
 
  +
'| [[%s|%s]]%s\n| %s',
return f_infocard(infocard_args)
 
  +
main_page,
  +
passive['passive_skills.name'],
  +
h.format_passive_icon(passive, h.get_type(passive)),
  +
passive['passive_skills.stat_text']
  +
)
  +
elseif tpl_args.format == nil then
  +
return
  +
else
  +
error(string.format('Invalid return format specified: %s', tpl_args.format))
  +
end
 
end
 
end
   
 
-- Not sure whether we need a more sophisticated variant like item or mod tables here yet
 
-- Not sure whether we need a more sophisticated variant like item or mod tables here yet
  +
  +
function p.passive_skill_table2(frame)
  +
-- Get args
  +
tpl_args = getArgs(frame, {
  +
parentFirst = true
  +
})
  +
frame = m_util.misc.get_frame(frame)
  +
  +
if tpl_args.q_tables then
  +
tpl_args.q_tables = tpl_args.q_tables .. ',' .. 'passive_skill_stats'
  +
else
  +
tpl_args.q_tables = 'passive_skill_stats'
  +
end
  +
  +
if tpl_args.q_join then
  +
tpl_args.q_join = tpl_args.q_join .. ',' .. 'passive_skills._pageID=passive_skill_stats._pageID'
  +
else
  +
tpl_args.q_join = 'passive_skills._pageID=passive_skill_stats._pageID'
  +
end
  +
  +
tpl_args.q_orderBy = 'passive_skills.ascendancy_class IS NULL DESC, passive_skills.is_keystone, passive_skills.is_notable, passive_skills.name'
  +
  +
return m_cargo.table_query{
  +
tpl_args=tpl_args,
  +
frame=frame,
  +
main_table='passive_skills',
  +
row_unique_fields = {'passive_skills.name'},
  +
data={
  +
tables = {
  +
passive_skill_stats = {
  +
join='passive_skills._pageID=passive_skill_stats._pageID',
  +
},
  +
},
  +
-- display data
  +
{
  +
args = {'ascendancy'},
  +
header = i18n.passive_table.ascendancy_class,
  +
fields = {
  +
'passive_skills.ascendancy_class',
  +
},
  +
display = function (tpl_args, frame, tr, data)
  +
local passive = data[1]
  +
if passive['passive_skills.ascendancy_class'] then
  +
tr:tag('td')
  +
:wikitext(string.format('[[%s]]<br>[[File:%s avatar.png|link=%s]]', passive['passive_skills.ascendancy_class'], passive['passive_skills.ascendancy_class'], passive['passive_skills.ascendancy_class']))
  +
else
  +
tr:wikitext(m_util.html.td.na{})
  +
end
  +
end,
  +
order = 0,
  +
sort_type = 'text',
  +
},
  +
{
  +
args = nil,
  +
header = i18n.passive_table.name,
  +
fields = {
  +
'passive_skills._pageName',
  +
'passive_skills.main_page',
  +
'passive_skills.name',
  +
'passive_skills.icon',
  +
'passive_skills.is_keystone',
  +
'passive_skills.is_notable',
  +
'passive_skills.ascendancy_class',
  +
},
  +
display = function (tpl_args, frame, tr, data)
  +
local passive = data[1]
  +
local type_key = h.get_type(passive)
  +
tr
  +
:tag('td')
  +
:attr('data-sort-value', passive['passive_skills.name'] .. type_key)
  +
:wikitext(string.format('[[%s|%s]]<br>%s', passive['passive_skills.main_page'] or passive['passive_skills.name'], passive['passive_skills.name'], h.format_passive_icon(passive, type_key)))
  +
:done()
  +
end,
  +
order = 1000,
  +
sort_type = 'text',
  +
options = {
  +
[7] = {
  +
optional=true,
  +
},
  +
},
  +
},
  +
{
  +
arg = {'default', 'stat', 'stats', 'stat_text'},
  +
header = i18n.passive_table.stats,
  +
fields = {
  +
'passive_skills.stat_text',
  +
},
  +
display = function (tpl_args, frame, tr, data)
  +
local passive = data[1]
  +
local stats, stat_order = h.make_stat_order(data)
  +
tr
  +
:tag('td')
  +
:wikitext(h.stat_page_links(stat_order, stats))
  +
:done()
  +
end,
  +
order = 1001,
  +
sort_type = 'text',
  +
}
  +
},
  +
}
  +
end
   
 
function p.passive_skill_table(frame)
 
function p.passive_skill_table(frame)
Line 350: Line 844:
 
tpl_args.ascendancy = m_util.cast.boolean(tpl_args.ascendancy)
 
tpl_args.ascendancy = m_util.cast.boolean(tpl_args.ascendancy)
 
 
local results = m_util.cargo.query(
+
local prepend = {
  +
q_join=true,
  +
}
  +
  +
local query = {
  +
join='passive_skills._pageID=passive_skill_stats._pageID',
  +
limit=5000,
  +
groupBy='passive_skills._pageID',
  +
}
  +
for key, value in pairs(tpl_args) do
  +
if string.sub(key, 0, 2) == 'q_' then
  +
if prepend[key] then
  +
value = ',' .. value
  +
end
  +
  +
query[string.sub(key, 3)] = value
  +
end
  +
end
  +
  +
local results = m_cargo.query(
 
{'passive_skills', 'passive_skill_stats'},
 
{'passive_skills', 'passive_skill_stats'},
 
{
 
{
 
'passive_skills._pageName',
 
'passive_skills._pageName',
  +
'passive_skills.main_page',
 
'passive_skills.name',
 
'passive_skills.name',
 
'passive_skills.stat_text',
 
'passive_skills.stat_text',
Line 361: Line 875:
 
'passive_skills.ascendancy_class',
 
'passive_skills.ascendancy_class',
 
},
 
},
{
+
query
where=tpl_args.q_where,
 
join='passive_skills._pageID=passive_skill_stats._pageID',
 
groupBy='passive_skills._pageID',
 
}
 
 
)
 
)
results = m_util.cargo.map_results_to_id{
+
result_map = m_cargo.map_results_to_id{
 
results=results,
 
results=results,
 
field='passive_skills.name',
 
field='passive_skills.name',
 
keep_id_field=true,
 
keep_id_field=true,
 
}
 
}
  +
for key, rows in pairs(result_map) do
  +
result_map[key] = h.sort_by_type(rows)
  +
end
 
 
  +
-- header
 
local tbl = mw.html.create('table')
 
local tbl = mw.html.create('table')
 
tbl:addClass('wikitable')
 
tbl:addClass('wikitable')
Line 391: Line 905:
 
:done()
 
:done()
 
 
for _, rows in pairs(results) do
+
-- rows
local row = rows[1]
+
local used_names = {}
  +
for _, passive_row in ipairs(results) do
tr = tbl:tag('tr')
 
+
local pn = passive_row['passive_skills.name']
if tpl_args.ascendancy then
+
if used_names[pn] == nil then
if row['passive_skills.ascendancy_class'] then
+
local type_results_map = result_map[passive_row['passive_skills.name']]
tr:tag('td')
+
used_names[pn] = true
  +
for _, type_key in ipairs(h.type_order) do
:wikitext(string.format('[[%s]]<br>[[File:%s avatar.png|link=%s]]', row['passive_skills.ascendancy_class'], row['passive_skills.ascendancy_class'], row['passive_skills.ascendancy_class']))
 
  +
local type_results = type_results_map[type_key]
else
 
tr:wikitext(m_util.html.td.na{})
+
if #type_results > 0 then
  +
local row = type_results[1]
  +
tr = tbl:tag('tr')
  +
  +
if tpl_args.ascendancy then
  +
if row['passive_skills.ascendancy_class'] then
  +
tr:tag('td')
  +
:wikitext(string.format('[[%s]]<br>[[File:%s avatar.png|link=%s]]', row['passive_skills.ascendancy_class'], row['passive_skills.ascendancy_class'], row['passive_skills.ascendancy_class']))
  +
else
  +
tr:wikitext(m_util.html.td.na{})
  +
end
  +
end
  +
  +
local stats, stat_order = h.make_stat_order(type_results)
  +
tr
  +
:tag('td')
  +
:attr('data-sort-value', row['passive_skills.name'] .. type_key)
  +
:wikitext(string.format('[[%s|%s]]<br>%s', row['passive_skills.main_page'] or row['passive_skills.name'], row['passive_skills.name'], h.format_passive_icon(row, type_key)))
  +
:done()
  +
:tag('td')
  +
:wikitext(h.stat_page_links(stat_order, stats))
  +
:done()
  +
end
 
end
 
end
 
end
 
end
 
local stats, stat_order = h.make_stat_order(rows)
 
tr
 
:tag('td')
 
:wikitext(string.format('[[%s]]<br>%s', row['passive_skills.name'], h.format_passive_icon(row)))
 
:done()
 
:tag('td')
 
:wikitext(h.stat_page_links(stat_order, stats))
 
:done()
 
 
end
 
end
 
 

Latest revision as of 20:59, 28 September 2019

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

--
-- Module for bestiary templates
--

local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')
local getArgs = require('Module:Arguments').getArgs
local f_infocard = require('Module:Infocard')._main

local p = {}

-- ----------------------------------------------------------------------------
-- Strings
-- ----------------------------------------------------------------------------

local i18n = {
    icon_name = 'File:%s passive skill icon.png',
    
    cats = {
        data = 'Passive skill data',
        
        keystone = 'Keystone passive skills',
        notable = 'Notable passive skills',
        basic = 'Basic passive skills',
        ascendancy_notable = 'Ascendancy notable passive skills',
        ascendancy_basic = 'Ascendancy basic passive skills',
    },
    
    passive_box = {
        keystone = 'Keystone',
        notable = 'Notable Passive Skill',
        basic = 'Passive Skill',
        ascendancy_notable = 'Ascendancy Notable Passive Skill',
        ascendancy_basic = 'Ascendancy Passive Skill',
    },
    
    intro = {
        text_with_name = "'''%s''' is the internal id for the [[%s|%s]] [[passive skill]]. ",
        text_without_name = "'''%s''' is the internal id of an unnamed [[passive skill]]. ",
    },
    
    passive_box_table = {
        id = 'Id',
        int_id = 'Integer Id',
        flavour_text = 'Flavour Text',
        reminder_text = 'Reminder Text',
        skill_points = 'Skill Points Granted',
        ascendancy_class = 'Ascendancy Class',
        connections = 'Connections',
    },
    
    passive_table = {
        ascendancy_class = 'Ascendancy<br>Class',
        name = 'Name',
        stats = 'Stats',
    },
    
    errors = {
        no_passives_found = 'No passive skills with the given name found',
    },
}

-- ----------------------------------------------------------------------------
-- Cargo
-- ----------------------------------------------------------------------------

local tables = {}

tables.passive_skills = {
    table = 'passive_skills',
    order = {'id', 'int_id', 'name', 'main_page', 'flavour_text', 'reminder_text', 'buff_id', 'skill_points', 'icon', 'ascendancy_class', 'is_keystone', 'is_notable', 'is_multiple_choice_option', 'is_multiple_choice', 'is_icon_only', 'is_jewel_socket', 'is_ascendancy_starting_node', 'stat_text', 'stat_text_raw', 'connections',},
    fields = {
        id = {
            field = 'id',
            type = 'String',
            required = true,
        },
        int_id = {
            field = 'int_id',
            type = 'Integer',
            required = true,
        },
        name = {
            field = 'name',
            type = 'String',
        },
        main_page = {
            field = 'main_page',
            type = 'Page',
        },
        flavour_text = {
            field = 'flavour_text',
            type = 'Text',
        },
        reminder_text = {
            field = 'reminder_text',
            type = 'Text',
        },
        buff_id = {
            field = 'buff_id',
            type = 'String',
        },
        -- TODO: Other buff stuff 
        skill_points = {
            field = 'skill_points',
            type = 'Integer',
            default = 0,
        },
        icon = {
            field = 'icon',
            type = 'Page',
            func = function(tpl_args, frame, value)
                if value then
                    return string.format(i18n.icon_name, value)
                end
            end
        },
        ascendancy_class = {
            field = 'ascendancy_class',
            type = 'String',
        },
        is_keystone = {
            field = 'is_keystone',
            type = 'Boolean',
            default = false,
        },
        is_notable = {
            field = 'is_notable',
            type = 'Boolean',
            default = false,
        },
        is_multiple_choice_option = {
            field = 'is_multiple_choice_option',
            type = 'Boolean',
            default = false,
        },
        is_multiple_choice = {
            field = 'is_multiple_choice',
            type = 'Boolean',
            default = false,
        },
        is_icon_only = {
            field = 'is_icon_only',
            type = 'Boolean',
            default = false,
        },
        is_jewel_socket = {
            field = 'is_jewel_socket',
            type = 'Boolean',
            default = false,
        },
        is_ascendancy_starting_node = {
            field = 'is_ascendancy_starting_node',
            type = 'Boolean',
            default = false,
        },
        stat_text = {
            field = 'stat_text',
            type = 'Text',
        },
        stat_text_raw = {
            field = 'stat_text',
            type = 'Text',
            func = function (tpl_args, frame)
                if tpl_args.stat_text then
                    tpl_args.stat_text_raw = string.gsub(
                        -- [[x]] -> x
                        string.gsub(
                            tpl_args.stat_text, '%[%[([^%]|]+)%]%]', '%1'
                        ), 
                        -- [[x|y]] -> y
                        '%[%[[^|]+|([^%]|]+)%]%]', '%1'
                    )
                end
                return tpl_args.stat_text_raw
            end
        },
        -- from the graph file:
        connections = {
            field = 'connections',
            type = 'List (,) of String',
        },
    }
}

tables.passive_skill_stats = {
    table = 'passive_skill_stats',
    fields = {
        id = {
            field = 'id',
            type = 'String',
        },
        value = {
            field = 'value',
            type = 'Integer',
        },
    }
}

local display = {}
display.map_to_property = {'icon', 'is_keystone', 'is_notable', 'ascendancy_class'}
display.tbl = {
    {
        key = 'id',
        header = i18n.passive_box_table.id,
        display = nil,
    },
    {
        key = 'int_id',
        header = i18n.passive_box_table.int_id,
        display = nil,
    },
    {
        css = 'tc -flavour',
        key = 'flavour_text',
        header = i18n.passive_box_table.flavour_text,
        display = nil,
    },
    {
        key = 'reminder_text',
        header = i18n.passive_box_table.reminder_text,
        display = nil,
    },
    {
        key = 'skill_points',
        header = i18n.passive_box_table.skill_points,
        display = nil,
    },
    {
        key = 'ascendancy_class',
        header = i18n.passive_box_table.ascendancy_class,
        display = function (tpl_args, frame, value)
            return string.format('[[%s]]', value)
        end,
    },
    {
        key = 'connections',
        header = i18n.passive_box_table.connections,
        display = function (tpl_args, frame, value)
            local results = m_cargo.map_results_to_id{
                field='passive_skills.id',
                results=m_cargo.array_query{
                    tables={'passive_skills'},
                    fields={'passive_skills.name', 'passive_skills._pageName'},
                    id_array=value,
                    id_field='passive_skills.id',
                    ignore_missing=true,
                }
            }
            
            local ul = mw.html.create('ul')
            for _, key in ipairs(value) do
                local row = results[key]
                if row then
                    row = row[1]
                end
                local text
                if row then
                    text = string.format('[[%s|%s]]', row['passive_skills._pageName'], row['passive_skills.name'] or row['passive_skills._pageName'])
                else
                    text = key
                end
                ul
                    :tag('li')
                        :wikitext(text)
                        :done()
            end
            
            return tostring(ul)
        end,
    },
}

-- ----------------------------------------------------------------------------
-- Helper functions
-- ----------------------------------------------------------------------------

local h = {}

function h.format_passive_icon(passive, passive_type)
    if passive['passive_skills.icon'] == nil then
        return ''
    end
    
    local cls = string.format('passive-icon-type__%s', passive_type)
    local main_page = passive['passive_skills.main_page'] or passive['main_pages._pageName'] or passive['passive_skills.name'] or passive['passive_skills.icon']
    div = mw.html.create('div')
    div:addClass('passive-icon-container')
    div:addClass(cls)
    div:tag('div')
        :addClass('passive-icon-frame')
        :done()
    div:wikitext(
        string.format(
            '[[%s|link=%s]]', 
            passive['passive_skills.icon'], 
            main_page
        )
    )
    
    return tostring(div)
end

function h.make_stat_order(results)
    local stats = {}
    local stat_order = {}
    for _, row in ipairs(results) do 
        local stat = row['passive_skills.stat_text']
        -- Can't show results here that don't have a stat line
        if stat then
            if stats[stat] == nil then
                stats[stat] = {row}
                table.insert(stat_order, stat)
            else
                table.insert(stats[stat], row)
            end
        end
    end
    
    return stats, stat_order
end

function h.stat_page_links(stat_order, stats)
    local out = {}
    for i, key in ipairs(stat_order) do
        local links = {}
        for j, row in ipairs(stats[key]) do
            links[#links+1] = string.format('[[%s|&#91;%s&#93;]]', row['passive_skills._pageName'], j)
        end
        out[i] = string.format('<span class="passive-line">%s <span class="passive-hover">%s</span></span>', key, table.concat(links, ' '))
    end
    
    return table.concat(out, '<hr>')
end

h.type_order = {'basic', 'notable', 'keystone', 'ascendancy_basic', 'ascendancy_notable'}
function h.get_type(passive)
    local key
    if tonumber(passive['passive_skills.is_keystone']) == 1 then
        key = 'keystone'
    elseif tonumber(passive['passive_skills.is_notable']) == 1 then
        key = 'notable'
    else
        key = 'basic'
    end
    
    if passive['passive_skills.ascendancy_class'] ~= nil then
        key = 'ascendancy_' .. key
    end
    
    return key
end

function h.sort_by_type(results)
    local new = {}
    for _, key in ipairs(h.type_order) do
        new[key] = {}
    end
    
    for _, passive in ipairs(results) do
        table.insert(new[h.get_type(passive)], passive)
    end
    
    return new
end

function h.intro_text(tpl_args, frame)
    --[[
    Display an introductory text about the passive skill.
    ]]
    local out = {}
    if mw.ustring.find(tpl_args['id'], '_') then
        out[#out+1] = frame:expandTemplate{
            title='Incorrect title', 
            args = {title=tpl_args['id']} 
        }
    end
    
    if tpl_args['name'] then
        out[#out+1] = string.format(
            i18n.intro.text_with_name, 
            tpl_args['id'], 
            tpl_args['main_page'] or tostring(mw.title.getCurrentTitle()), 
            tpl_args['name']
        )
    else 
        out[#out+1] = string.format(
            i18n.intro.text_without_name,
            tpl_args['id']
        )
    end 
    
    return table.concat(out)
end

function h.stat_box(tpl_args, frame)
    --[[
    Display the stat box.
    ]]
    local container = mw.html.create('div')
    container
        :attr('class', 'modbox floatright')
        
    -- stat table 
    tbl = container:tag('table')
    tbl
        :attr('class', 'wikitable sortable')
        -- :attr('style', 'style="width: 100%;"')
        :tag('tr')
            :tag('th')
                :attr('colspan', 3)
                :wikitext('Stats')
                :done()
            :done()
        :tag('tr')
            :tag('th')
                :wikitext('#')
                :done()
            :tag('th')
                :wikitext('Stat Id')
                :done()
            :tag('th')
                :wikitext('Value')
                :done()
            :done()
            :done()
        :done()
        
    local i = 0
    local value = nil
    repeat
        i = i + 1
        value = {
            id = tpl_args[string.format('stat%s_id', i)],
            value = tpl_args[string.format('stat%s_value', i)],
        }
        
        if value.id then
            tbl
                :tag('tr')
                    :tag('td')
                        :wikitext(i)
                        :done()
                    :tag('td')
                        :wikitext(value.id)
                        :done()
                    :tag('td')
                        :wikitext(value.value)
                        :done()
                    :done()
                :done()
        end
    until value.id == nil
    
    return tostring(container)
end


-- ----------------------------------------------------------------------------
-- Page functions
-- ----------------------------------------------------------------------------

local p = {}

-- This way the helper functions can be used in other modules
p.h = h

-- Declare cargo tables:
p.table_passive_skills = m_cargo.declare_factory{data=tables.passive_skills}
p.table_passive_skill_stats = m_cargo.declare_factory{data=tables.passive_skill_stats}

function p.passive_skill(frame)
    --[[
    Stores data and displays a infobox about the passive skill.
    
    Examples
    --------
    = p.passive_skill{
        id = 'life_life_leech1629',
        int_id = '27788',
        name = 'Blood Drinker',
        is_notable = 'True',
        icon = 'lifeleech',
        stat1_id = 'maximum_life_+%',
        stat1_value = '8',
        stat2_id = 'base_life_leech_from_attack_damage_permyriad',
        stat2_value = '40',
        stat_text = '8% increased maximum life<br>0.4% of Attack Damage Leeched as Life',
        connections = 'life1415,life1413',
    }
    
    ]]

    -- Get args
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    -- parse 
    m_util.args.from_cargo_map{
        tpl_args=tpl_args,
        frame=frame,
        table_map=tables.passive_skills,
    }
    
    -- parse stats
    m_util.args.stats(tpl_args, {})
    for _, stat in ipairs(tpl_args.stats) do
        stat._table = tables.passive_skill_stats.table
        m_cargo.store(frame, stat)
    end
    
    --
    -- Infobox
    --
    local passive = {}
    for _, key in ipairs(display.map_to_property) do
        local v = tpl_args[key]
        if type(v) == 'boolean' then
            if v then
                v = 1
            else
                v = 0
            end
        end
        passive[string.format('%s.%s', tables.passive_skills.table, tables.passive_skills.fields[key].field)] = v
    end
    
    local type_key = h.get_type(passive)
    
    local infocard_args = {}
    infocard_args.header = tpl_args.name
    infocard_args.subheader = i18n.passive_box[type_key]
    
    local tbl = mw.html.create('table')
    for _, data in ipairs(display.tbl) do
        local value = tpl_args[data.key]
        -- if default is nil, this will be compared against nil which is what we want, so value ~= nil isn't needed
        if value ~= tables.passive_skills.fields[data.key].default then
            local dsp
            if data.display then
                dsp = data.display(tpl_args, frame, value)
            else
                dsp = value
            end
            tbl
                :tag('tr')
                    :tag('th')
                        :wikitext(data.header)
                        :done()
                    :tag('td')
                        :attr('class', data.css)
                        :wikitext(dsp)
                        :done()
                    :done()
        end
    end
    
    infocard_args[1] = tostring(tbl)
    infocard_args[2] = tpl_args.stat_text
    infocard_args[3] = h.format_passive_icon(passive, type_key)

    local out = {
        f_infocard(infocard_args),
        h.intro_text(tpl_args, frame),
        h.stat_box(tpl_args, frame),
    }
    
    local cats = {
        i18n.cats.data,
    }
    return table.concat(out) .. m_util.misc.add_category(cats)
end

function p.passive_skill_box(frame)
    --[[
    Queries a passive skill and displays it.
    
    Examples
    --------
    = p.passive_skill_box{name='Ghost Reaver'}
    
    ]]
    
    -- Get args
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    tpl_args.name = tpl_args.name or tpl_args[1]
    
    if not tpl_args.q_where and tpl_args.name then
        tpl_args.q_where = string.format('passive_skills.name="%s" AND passive_skills.stat_text IS NOT NULL', tpl_args.name)
    elseif not (tpl_args.q_where and not tpl_args.name) then
        error('q_where or name must be specified')
    end
    
    local results = m_cargo.query(
        {'passive_skills'},
        {
            'passive_skills._pageName', 
            'passive_skills.main_page',
            'passive_skills.name', 
            'passive_skills.stat_text',
            -- TODO: only really need these once, maybe put in extra query
            'passive_skills.flavour_text',
            'passive_skills.icon',
            'passive_skills.is_keystone',
            'passive_skills.is_notable',
            'passive_skills.ascendancy_class',
            'passive_skills.id'
        }, 
        {
            where=tpl_args.q_where,
            orderBy='passive_skills.stat_text',
            limit=5000,
        }
    )
    if #results == 0 then
        error(i18n.errors.no_passives_found)
    end
    
    results = h.sort_by_type(results)
    local out = {}
    local cats = {}
    for _, type_key in ipairs(h.type_order) do
        local type_results = results[type_key]
        if #type_results > 0 then
            cats[#cats+1] = i18n.cats[type_key]
            local stats, stat_order = h.make_stat_order(type_results)
            
            local passive = type_results[1]
            
            local infocard_args = {}
            infocard_args.header = passive['passive_skills.name']
            infocard_args.subheader = i18n.passive_box[type_key]
            
            infocard_args[1] = h.format_passive_icon(passive, type_key)
            infocard_args[2] = h.stat_page_links(stat_order, stats)
            infocard_args[3] = m_util.html.poe_color('flavour', passive['passive_skills.flavour_text'])
            
            out[#out+1] = f_infocard(infocard_args)
            
            -- Store as main page:
            for _, v in ipairs(type_results) do
                m_cargo.store(
                    frame, 
                    {
                        _table='main_pages', 
                        id=v['passive_skills.id'],
                    }
                )
            end
        end
    end
    
    if tpl_args.cats == nil or m_util.cast.boolean(tpl_args.cats) then
        out[#out+1] = m_util.misc.add_category(cats)
    end
    
    return table.concat(out)
end

function p.passive_skill_link(frame)
    --[[
    Links a passive skill
    
    Examples
    --------
    = p.passive_skill_link{id='AscendancyAscendant45', format='tablerow'}
    ]]
    
    -- Get args
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    tpl_args.name = tpl_args.name or tpl_args[1]
    if tpl_args.name then
        tpl_args.q_where = string.format('passive_skills.name="%s"', tpl_args.name)
    elseif tpl_args.id then
        tpl_args.q_where = string.format('passive_skills.id="%s"', tpl_args.id)
    elseif tpl_args.q_where then
    else
        error('Either name, id or q_where must be specified')
    end
    
    local results = m_cargo.query(
        {'passive_skills', 'main_pages'},
        {
            'passive_skills._pageName', 
            'passive_skills.stat_text',
            'passive_skills.main_page',
            'passive_skills.name',
            'passive_skills.icon',
            'passive_skills.is_keystone',
            'passive_skills.is_notable',
            'passive_skills.ascendancy_class',
            'main_pages._pageName',
        }, 
        {   
            join='passive_skills.id=main_pages.id',
            where=string.format('(%s) AND passive_skills.stat_text IS NOT NULL', tpl_args.q_where),
            orderBy='passive_skills.stat_text',
            limit=2,
        }
    )
    
    if #results > 1 then
        error('Too many passives found!')
    elseif #results < 1 then
        error('No passives found')
    end
    local passive = results[1]
    
    if tpl_args.format == 'tablerow' then
        local main_page = passive['passive_skills.main_page'] or passive['main_pages._pageName'] or passive['passive_skills.name']
        return string.format(
            '| [[%s|%s]]%s\n| %s', 
            main_page, 
            passive['passive_skills.name'], 
            h.format_passive_icon(passive, h.get_type(passive)), 
            passive['passive_skills.stat_text']
        )
    elseif tpl_args.format == nil then
        return
    else
        error(string.format('Invalid return format specified: %s', tpl_args.format))
    end
end

-- Not sure whether we need a more sophisticated variant like item or mod tables here yet

function p.passive_skill_table2(frame)
    -- Get args
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    if tpl_args.q_tables then 
        tpl_args.q_tables = tpl_args.q_tables .. ',' .. 'passive_skill_stats'
    else
        tpl_args.q_tables = 'passive_skill_stats'
    end
    
    if tpl_args.q_join then 
        tpl_args.q_join = tpl_args.q_join .. ',' .. 'passive_skills._pageID=passive_skill_stats._pageID'
    else
        tpl_args.q_join = 'passive_skills._pageID=passive_skill_stats._pageID'
    end 
    
    tpl_args.q_orderBy = 'passive_skills.ascendancy_class IS NULL DESC, passive_skills.is_keystone, passive_skills.is_notable, passive_skills.name'
    
    return m_cargo.table_query{
        tpl_args=tpl_args,
        frame=frame,
        main_table='passive_skills',
        row_unique_fields = {'passive_skills.name'},
        data={
            tables = {
                passive_skill_stats = {
                    join='passive_skills._pageID=passive_skill_stats._pageID',
                },
            },
            -- display data
            {
                args = {'ascendancy'},
                header = i18n.passive_table.ascendancy_class,
                fields = {
                    'passive_skills.ascendancy_class',
                },
                display = function (tpl_args, frame, tr, data)
                    local passive = data[1]
                    if passive['passive_skills.ascendancy_class'] then
                        tr:tag('td')
                            :wikitext(string.format('[[%s]]<br>[[File:%s avatar.png|link=%s]]', passive['passive_skills.ascendancy_class'], passive['passive_skills.ascendancy_class'], passive['passive_skills.ascendancy_class']))
                    else
                        tr:wikitext(m_util.html.td.na{})
                    end
                end,
                order = 0,
                sort_type = 'text',
            },
            {
                args = nil,
                header = i18n.passive_table.name,
                fields = {
                    'passive_skills._pageName',
                    'passive_skills.main_page',
                    'passive_skills.name',
                    'passive_skills.icon',
                    'passive_skills.is_keystone',
                    'passive_skills.is_notable',
                    'passive_skills.ascendancy_class',
                },
                display = function (tpl_args, frame, tr, data)
                    local passive = data[1]
                    local type_key = h.get_type(passive)
                    tr
                        :tag('td')
                            :attr('data-sort-value', passive['passive_skills.name'] .. type_key)
                            :wikitext(string.format('[[%s|%s]]<br>%s', passive['passive_skills.main_page'] or passive['passive_skills.name'], passive['passive_skills.name'], h.format_passive_icon(passive, type_key)))
                            :done()
                end,
                order = 1000,
                sort_type = 'text',
                options = {
                    [7] = {
                        optional=true,
                    },
                },
            },
            {
                arg = {'default', 'stat', 'stats', 'stat_text'},
                header = i18n.passive_table.stats,
                fields = {
                    'passive_skills.stat_text',
                },
                display = function (tpl_args, frame, tr, data)
                    local passive = data[1]
                    local stats, stat_order = h.make_stat_order(data)
                    tr
                        :tag('td')
                            :wikitext(h.stat_page_links(stat_order, stats))
                            :done()
                end,
                order = 1001,
                sort_type = 'text',
            }
        },
    }
end

function p.passive_skill_table(frame)
    -- Get args
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    tpl_args.ascendancy = m_util.cast.boolean(tpl_args.ascendancy)
    
    local prepend = {
        q_join=true,
    }
    
    local query = {
        join='passive_skills._pageID=passive_skill_stats._pageID',
        limit=5000,
        groupBy='passive_skills._pageID',
    }
    for key, value in pairs(tpl_args) do 
        if string.sub(key, 0, 2) == 'q_' then
            if prepend[key] then
                value = ',' .. value
            end
            
            query[string.sub(key, 3)] = value
        end
    end
    
    local results = m_cargo.query(
        {'passive_skills', 'passive_skill_stats'},
        {
            'passive_skills._pageName', 
            'passive_skills.main_page',
            'passive_skills.name',
            'passive_skills.stat_text',
            'passive_skills.icon',
            'passive_skills.is_keystone',
            'passive_skills.is_notable',
            'passive_skills.ascendancy_class',
        }, 
        query
    )
    result_map = m_cargo.map_results_to_id{
        results=results,
        field='passive_skills.name',
        keep_id_field=true,
    }
    for key, rows in pairs(result_map) do
        result_map[key] = h.sort_by_type(rows)
    end
    
    -- header
    local tbl = mw.html.create('table')
    tbl:addClass('wikitable')
    tbl:addClass('sortable')
    
    local tr = tbl:tag('tr')
    if tpl_args.ascendancy then
        tr:tag('th')
            :wikitext(i18n.passive_table.ascendancy_class)
    end
    
    tr
        :tag('th')
            :wikitext(i18n.passive_table.name)
            :done()
        :tag('th')
            :wikitext(i18n.passive_table.stats)
            :done()
    
    -- rows
    local used_names = {}
    for _, passive_row in ipairs(results) do
        local pn = passive_row['passive_skills.name']
        if used_names[pn] == nil then
            local type_results_map = result_map[passive_row['passive_skills.name']]
            used_names[pn] = true
            for _, type_key in ipairs(h.type_order) do
                local type_results = type_results_map[type_key]
                if #type_results > 0 then
                    local row = type_results[1]
                    tr = tbl:tag('tr')
                        
                    if tpl_args.ascendancy then
                        if row['passive_skills.ascendancy_class'] then
                            tr:tag('td')
                                :wikitext(string.format('[[%s]]<br>[[File:%s avatar.png|link=%s]]', row['passive_skills.ascendancy_class'], row['passive_skills.ascendancy_class'], row['passive_skills.ascendancy_class']))
                        else
                            tr:wikitext(m_util.html.td.na{})
                        end
                    end
                    
                    local stats, stat_order = h.make_stat_order(type_results)
                    tr
                        :tag('td')
                            :attr('data-sort-value', row['passive_skills.name'] .. type_key)
                            :wikitext(string.format('[[%s|%s]]<br>%s', row['passive_skills.main_page'] or row['passive_skills.name'], row['passive_skills.name'], h.format_passive_icon(row, type_key)))
                            :done()
                        :tag('td')
                            :wikitext(h.stat_page_links(stat_order, stats))
                            :done()
                end
            end
        end
    end
    
    return tostring(tbl)
end

-- ----------------------------------------------------------------------------
-- End
-- ----------------------------------------------------------------------------

return p