Module:Passive skill table: Difference between revisions

From Path of Exile 2 Wiki
Jump to navigation Jump to search
>Illviljan
No edit summary
No edit summary
(16 intermediate revisions by 5 users not shown)
Line 1: Line 1:
--
-------------------------------------------------------------------------------
-- Module for passive skill table
--  
--
--                             Module:Passive skill table
--
-- This module implements [[Template:Passive skill table]] and other templates
-- that query and display tables or lists of passive skills.
-------------------------------------------------------------------------------


require('strict')
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 f_passive_skill_link = require('Module:passive_skill_link').passive_skill_link


-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Passive skill table')


local p = {}
local m_cargo = use_sandbox and require('Module:Cargo/sandbox') or require('Module:Cargo')
 
-- ----------------------------------------------------------------------------
-- 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_table = {
        ascendancy_class = 'Ascendancy<br>Class',
        name = 'Name',
        id = 'Id',
        int_id = 'Integer id',
        stats = 'Stats',
        connections = 'Connections',
        is_notable = 'Notable',
        is_keystone = 'Keystone',
    },
   
    errors = {
        invalid_args = 'Passive skill link: id, int_id, page or name must be specified',
        no_passives_found = 'No passive skills with the given name found',
    },
}


local f_passive_skill_link = require('Module:passive_skill_link').passive_skill_link


-- ----------------------------------------------------------------------------
-- The cfg table contains all localisable strings and configuration, to make it
-- Constants & Data
-- easier to port this module to another wiki.
-- ----------------------------------------------------------------------------
local cfg = use_sandbox and mw.loadData('Module:Passive skill table/config/sandbox') or mw.loadData('Module:Passive skill table/config')


-- local c = {}
local i18n = cfg.i18n


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
Line 68: Line 39:
         if args.fmt then
         if args.fmt then
             val = args.fmt(row[field])
             val = args.fmt(row[field])
         end  
         end
         -- Can't show results here that don't have a value:
         -- Can't show results here that don't have a value:
         if val then
         if val then
Line 79: Line 50:
         end
         end
     end
     end
   
 
     return values, order
     return values, order
end
end
Line 89: Line 60:
         for j, row in ipairs(values[key]) do
         for j, row in ipairs(values[key]) do
             links[#links+1] = string.format(
             links[#links+1] = string.format(
                 '[[%s|&#91;%s&#93;]]',  
                 '[[%s|&#91;%s&#93;]]',
                 row['passive_skills._pageName'],  
                 row['passive_skills._pageName'],
                 j + (i-1) * #values[key]
                 j + (i-1) * #values[key]
             )
             )
         end
         end
         out[i] = string.format(
         out[i] = string.format(
             '<span class="passive-line">%s <span class="passive-hover">%s</span></span>',  
             '<span class="passive-line">%s <span class="passive-hover">%s</span></span>',
             key,  
             key,
             table.concat(links, ' ')
             table.concat(links, ' ')
         )
         )
     end
     end
   
 
     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)
function h.get_type(passive)
    --[[
    Determine what type of passive skill this passive is.
    ]]
     local key
     local key
     if tonumber(passive['passive_skills.is_keystone']) == 1 then
     if tonumber(passive['passive_skills.is_keystone']) == 1 then
Line 120: Line 87:
         key = 'basic'
         key = 'basic'
     end
     end
   
 
     if passive['passive_skills.ascendancy_class'] ~= nil then
     if passive['passive_skills.ascendancy_class'] ~= nil then
         key = 'ascendancy_' .. key
         key = 'ascendancy_' .. key
    elseif tonumber(passive['passive_skills.is_atlas_passive']) == 1 then
        key = 'atlas_' .. key
     end
     end
   
 
     return key
     return key
end
end


function h.sort_by_type(results)
function h.na_or_val(tr, value, func, args)
     local new = {}
     --[[
     for _, key in ipairs(h.type_order) do
 
        new[key] = {}
     Parameters
     end
    ----------
   
     tr :
     for _, passive in ipairs(results) do
 
        table.insert(new[h.get_type(passive)], passive)
     value :
     end
 
      
     func : function
    return new
 
end
     args : list of
        Valid keys are:
        * colour


function h.na_or_val(tr, value, func)
    ]]
    local args = args or {}
     if value == nil or value == '' then
     if value == nil or value == '' then
         tr:wikitext(m_util.html.td.na())
         tr:node(m_util.html.table_cell('na'))
     else
     else
         local raw_value = value
         local raw_value = value
Line 149: Line 121:
             value = func(value)
             value = func(value)
         end
         end
         tr
 
            :tag('td')
         local td = tr:tag('td')
                :attr('data-sort-value', raw_value)
        td:attr('data-sort-value', raw_value)
                :wikitext(value)
        td:wikitext(value)
                :done()
        if args.colour then
            td:attr('class', 'tc -' .. args.colour)
        end
     end
     end
end
end
Line 161: Line 135:
function h.fmt.link(field)
function h.fmt.link(field)
     local fields = m_util.string.split(field, ',')
     local fields = m_util.string.split(field, ',')
     for i, v in ipairs(fields) do  
     for i, v in ipairs(fields) do
         fields[i] = string.format('[[%s]]', v)
         fields[i] = string.format('[[%s]]', v)
     end  
     end
     return table.concat(fields, ', ')
     return table.concat(fields, ', ')
end  
end
function h.fmt.passive_skill_link(field)
function h.fmt.passive_skill_link(field)
     local fields = m_util.string.split(field, ',')
     local fields = m_util.string.split(field, ',')
     for i, v in ipairs(fields) do
     for i, v in ipairs(fields) do
         fields[i] = f_passive_skill_link{id=v}
         fields[i] = f_passive_skill_link{id=v}
     end  
     end
     return table.concat(fields, ', ')
     return table.concat(fields, ', ')
end
end
Line 178: Line 152:
         if m_util.cast.boolean(v) then
         if m_util.cast.boolean(v) then
             fields[i] = '<div class="table-yes" data-sort-value=1><span>&#x2713;</span></div>'
             fields[i] = '<div class="table-yes" data-sort-value=1><span>&#x2713;</span></div>'
         else  
         else
             fields[i] = '<div class="table-no" data-sort-value=0><span>&#x2717;</span></div>'
             fields[i] = '<div class="table-no" data-sort-value=0><span>&#x2717;</span></div>'
         end
         end
     end  
     end
     return table.concat(fields, ', ')
     return table.concat(fields, ', ')
end
end
function h.fmt.link_passive(field)
function h.fmt.link_passive(field)
     --[[
     --[[
     Create a link to the passive skill data page from the ID.  
     Create a link to the passive skill data page from the ID.
     ]]
     ]]
     local fields = m_util.string.split(field, ',')
     local fields = m_util.string.split(field, ',')
Line 195: Line 169:
             v
             v
         )
         )
     end  
     end
     return table.concat(fields, ', ')
     return table.concat(fields, ', ')
end
end


-- Display  
-- Display
h.tbl = {}
h.tbl = {}
h.tbl.display = {}
h.tbl.display = {}
function h.tbl.display.merged_values(args)
 
h.tbl.display.factory = {}
function h.tbl.display.factory.merged_values(args)
     local args = args or {}
     local args = args or {}
     return function (tpl_args, frame, tr, rows, rowinfo)
     return function (tpl_args, tr, rows, rowinfo)
         local values, order = h.make_order(rows, args.field, args)
         local values, order = h.make_order(rows, args.field, args)
         h.na_or_val(tr, h.data_page_links(values, order), nil)
         local value = h.data_page_links(values, order)
        h.na_or_val(tr, value, nil, args)
     end
     end
end
end


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- Page functions
-- Data mappings
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------


local p = {}
local data_map = {}
 
data_map.passive_skill_table = {
 
data = {}
data.passive_skill_table = {
     tables = {
     tables = {
         passive_skill_stats = {
         passive_skill_stats = {
             join='passive_skills._pageID=passive_skill_stats._pageID',
             join='passive_skills._pageID=passive_skill_stats._pageID',
         },
         },
        main_pages = {
            join='passive_skills.id=main_pages.id'
        }
     },
     },
     -- display data
     -- Display data
     {
     {
        order = 1000,
         args = nil,
         args = nil,
         header = i18n.passive_table.name,
         header = i18n.passive_table.name,
Line 233: Line 211:
             'passive_skills.is_notable',
             'passive_skills.is_notable',
             'passive_skills.ascendancy_class',
             'passive_skills.ascendancy_class',
            'passive_skills.is_atlas_passive',
             'passive_skills.main_page',
             'passive_skills.main_page',
             'main_pages._pageName',
             'main_pages._pageName',
            'passive_skills.icon',
             -- 'passive_skills.html',
             -- 'passive_skills.html',
           
 
             -- Required:
             -- Required:
             'passive_skills._pageName',
             'passive_skills._pageName',
             'passive_skills.name',
             'passive_skills.name',
            'passive_skills.icon',
         },
         },      
         display = function (tpl_args, tr, data)
         display = function (tpl_args, frame, tr, data)
             local passive = data[1]
             local passive = data[1]
            local type_key = h.get_type(passive)
 
           
             local psl_args = {
             local psl_args = {
                 skip_query = true,
                 skip_query = true,
                 page = passive['passive_skills.main_page']  
                 page = passive['passive_skills.main_page']
                     or passive['main_pages._pageName']  
                     or passive['main_pages._pageName']
                     or passive['passive_skills.name']
                     or passive['passive_skills.name']
                     or passive['passive_skills._pageName'],  
                     or passive['passive_skills._pageName'],
                 name = passive['passive_skills.name'],  
                 name = passive['passive_skills.name'],
                 icon = passive['passive_skills.icon'],  
                 icon = passive['passive_skills.icon'],
                 is_keystone = passive['passive_skills.is_keystone'],
                 is_keystone = passive['passive_skills.is_keystone'],
                 is_notable = passive['passive_skills.is_notable'],
                 is_notable = passive['passive_skills.is_notable'],
                 ascendancy_class = passive['passive_skills.ascendancy_class'],
                 ascendancy_class = passive['passive_skills.ascendancy_class'],
                 html = passive['passive_skills.is_notable'],
                 is_atlas_passive = passive['passive_skills.is_atlas_passive'],
             }
             }
             if tpl_args.no_html == nil then
             if tpl_args.no_html == nil then
Line 265: Line 243:
                 psl_args.large = tpl_args.large
                 psl_args.large = tpl_args.large
             end
             end
               
 
             tr
             tr
                 :tag('td')
                 :tag('td')
                     :attr(
                     :attr(
                         'data-sort-value',  
                         'data-sort-value',
                         passive['passive_skills.name'] .. type_key
                         passive['passive_skills.name'] .. h.get_type(passive)
                     )
                     )
                    :attr('style', 'text-align:center;')
                     :wikitext(f_passive_skill_link(psl_args))
                     :wikitext(f_passive_skill_link(psl_args))
                     :done()
                     :done()
           
 
         end,
         end,
        order = 1000,
         sort_type = 'text',
         sort_type = 'text',
         options = {
         options = {
Line 284: Line 262:
             [4] = {optional=true},
             [4] = {optional=true},
             [5] = {optional=true},
             [5] = {optional=true},
            [6] = {optional=true},
         },
         },
     },
     },
     {
     {
        order = 0,
         args = {'ascendancy'},
         args = {'ascendancy'},
         header = i18n.passive_table.ascendancy_class,
         header = i18n.passive_table.ascendancy_class,
         fields = {'passive_skills.ascendancy_class'},
         fields = {'passive_skills.ascendancy_class'},
         display = function (tpl_args, frame, tr, data)
         display = function (tpl_args, tr, data)
             local passive = data[1]
             local passive = data[1]
             h.na_or_val(tr, passive['passive_skills.ascendancy_class'],  
             h.na_or_val(tr, passive['passive_skills.ascendancy_class'],
                 function ()
                 function ()
                     return string.format(
                     return string.format(
                         '[[%s]]<br>[[File:%s avatar.png|link=%s]]',  
                         '[[%s]]<br>[[File:%s avatar.png|link=%s]]',
                         passive['passive_skills.ascendancy_class'],  
                         passive['passive_skills.ascendancy_class'],
                         passive['passive_skills.ascendancy_class'],  
                         passive['passive_skills.ascendancy_class'],
                         passive['passive_skills.ascendancy_class']
                         passive['passive_skills.ascendancy_class']
                     )
                     )
Line 303: Line 283:
             )
             )
         end,
         end,
        order = 0,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
        order = 1001,
         args = 'id',
         args = 'id',
         header = i18n.passive_table.id,
         header = i18n.passive_table.id,
         fields = {'passive_skills.id'},              
         fields = {'passive_skills.id'},
         display = h.tbl.display.merged_values{field='passive_skills.id'},
         display = h.tbl.display.factory.merged_values{field='passive_skills.id'},
        order = 1001,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
        order = 1002,
         args = 'int_id',
         args = 'int_id',
         header = i18n.passive_table.int_id,
         header = i18n.passive_table.int_id,
         fields = {'passive_skills.int_id'},      
         fields = {'passive_skills.int_id'},
         display = h.tbl.display.merged_values{field='passive_skills.int_id'},
         display = h.tbl.display.factory.merged_values{field='passive_skills.int_id'},
        order = 1002,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = {'stat', 'stats', 'stat_text'},
         order = 2000,
        args = {'stat', 'stats', 'stat_text'},
         header = i18n.passive_table.stats,
         header = i18n.passive_table.stats,
         fields = {'passive_skills.stat_text'},
         fields = {'passive_skills.stat_text'},
         display = h.tbl.display.merged_values{field='passive_skills.stat_text'},
         display = h.tbl.display.factory.merged_values{field='passive_skills.stat_text', colour='mod'},
        order = 2000,
         sort_type = 'text',
         sort_type = 'text',
     },
     },
     {
     {
         arg = 'connections',
         order = 2001,
         header = 'Connections',
        args = 'skill_points',
        header = i18n.passive_table.skill_points,
        fields = {'passive_skills.skill_points'},
        display = h.tbl.display.factory.merged_values{field='passive_skills.skill_points'},
        sort_type = 'number',
    },
    {
        order = 2002,
        args = 'weapon_set_points',
         header = i18n.passive_table.weapon_set_points,
        fields = {'passive_skills.weapon_set_points'},
        display = h.tbl.display.factory.merged_values{field='passive_skills.weapon_set_points'},
        sort_type = 'number',
    },
    {
        order = 3000,
        args = 'connections',
        header = i18n.passive_table.connections,
         fields = {'passive_skills.connections'},
         fields = {'passive_skills.connections'},
         display = h.tbl.display.merged_values{
         display = h.tbl.display.factory.merged_values{
             field='passive_skills.connections',
             field='passive_skills.connections',
            -- fmt=h.fmt.link_passive,
             fmt=h.fmt.passive_skill_link,
             fmt=h.fmt.passive_skill_link,
         },
         },
         order = 3000,
        sort_type = 'text',
         sort_type = 'text',  
    },
    {
        order = 3001,
        args = 'flavour_text',
        header = i18n.passive_table.flavour_text,
        fields = {'passive_skills.flavour_text'},
        display = h.tbl.display.factory.merged_values{
            field='passive_skills.flavour_text',
            colour='flavour',
        },
        sort_type = 'text',
    },
    {
         order = 3002,
        args = 'reminder_text',
        header = i18n.passive_table.reminder_text,
        fields = {'passive_skills.reminder_text'},
        display = h.tbl.display.factory.merged_values{
            field='passive_skills.reminder_text',
            colour='help',
        },
         sort_type = 'text',
     },
     },
     {
     {
        order = 4000,
         args = 'is_notable',
         args = 'is_notable',
         header = i18n.passive_table.is_notable,
         header = i18n.passive_table.is_notable,
         fields = {'passive_skills.is_notable'},      
         fields = {'passive_skills.is_notable'},
         display = h.tbl.display.merged_values{
         display = h.tbl.display.factory.merged_values{
             field='passive_skills.is_notable',  
             field='passive_skills.is_notable',
             fmt=h.fmt.boolean
             fmt=h.fmt.boolean
         },
         },
        order = 4000,
         sort_type = 'number',
         sort_type = 'number',
     },
     },
     {
     {
        order = 4001,
         args = 'is_keystone',
         args = 'is_keystone',
         header = i18n.passive_table.is_keystone,
         header = i18n.passive_table.is_keystone,
         fields = {'passive_skills.is_keystone'},      
         fields = {'passive_skills.is_keystone'},
         display = h.tbl.display.merged_values{
         display = h.tbl.display.factory.merged_values{
             field='passive_skills.is_keystone',  
             field='passive_skills.is_keystone',
             fmt=h.fmt.boolean
             fmt=h.fmt.boolean
         },
         },
        order = 4001,
         sort_type = 'number',
         sort_type = 'number',
     },
     },
}
}


function p.passive_skill_table(frame)
-- ----------------------------------------------------------------------------
-- Main functions
-- ----------------------------------------------------------------------------
 
local function _passive_skill_table(tpl_args)
     --[[
     --[[
     Displays a table of passive skills.
     Displays a table of passive skills.
   
 
     TODO: Why is the groupBy necessary?
     TODO: Why is the groupBy necessary?
   
 
     Examples
     Examples
     --------
     --------
     = p.passive_skill_table{
     = p.passive_skill_table{
         q_where = 'passive_skills.ascendancy_class = "Gladiator"',
         q_where = 'passive_skills.ascendancy_class = "Gladiator"',
         name=1,
         large=1,
    }
    = p.passive_skill_table{
        q_where='passive_skills.stat_text LIKE "%damage%taken%as%"',
        ascendancy=1,
        stat_text=1,
     }
     }
   
 
     ]]
     ]]
     local t = os.clock()
     local t = os.clock()
   
 
     -- Get args
     -- Check if the correct parameters have been set:
    tpl_args = getArgs(frame, {
     if tpl_args.q_where == nil then
        parentFirst = true
         return m_util.html.error{msg=i18n.errors.invalid_args .. m_util.misc.add_category(i18n.errors.category)}
    })
    frame = m_util.misc.get_frame(frame)
   
    -- Create cargo query:
     if tpl_args.q_tables then
        tpl_args.q_tables = tpl_args.q_tables .. ',' .. 'passive_skill_stats, main_pages'
    else
        tpl_args.q_tables = 'passive_skill_stats, main_pages'
    end
    if tpl_args.q_join then  
         tpl_args.q_join = tpl_args.q_join .. ',' .. 'passive_skills._pageID=passive_skill_stats._pageID, passive_skills.id=main_pages.id'
    else
        tpl_args.q_join = 'passive_skills._pageID=passive_skill_stats._pageID, passive_skills.id=main_pages.id'
     end
     end
    -- Cargo query settings:
     tpl_args.q_groupBy = tpl_args.q_groupBy or 'passive_skills._pageID'
     tpl_args.q_groupBy = tpl_args.q_groupBy or 'passive_skills._pageID'
     tpl_args.q_orderBy = tpl_args.q_orderBy or 'passive_skills.ascendancy_class IS NULL DESC, passive_skills.is_keystone, passive_skills.is_notable, passive_skills.name'
     tpl_args.q_orderBy = tpl_args.q_orderBy or [[
 
        passive_skills.ascendancy_class IS NULL DESC,
        passive_skills.is_keystone,
        passive_skills.is_notable,
        passive_skills.name
    ]]
 
     -- Run the query and display the results:
     -- Run the query and display the results:
     out = m_cargo.table_query{
     local out = m_cargo.table_query{
         tpl_args=tpl_args,
         tpl_args=tpl_args,
        frame=frame,
         main_table='passive_skills',
         main_table='passive_skills',
         row_unique_fields={'passive_skills.name'},
         row_unique_fields={'passive_skills.name'},
         data=data.passive_skill_table,
         data=data_map.passive_skill_table,
         empty_cell=m_util.html.td.na()  
         empty_cell=tostring(m_util.html.table_cell('na'))
     }
     }
   
 
     mw.logObject({os.clock() - t, tpl_args})
     mw.logObject({os.clock() - t, tpl_args})


     return out  
     return out
end
end




-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- End
-- Exported functions
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
local p = {}
--
-- Template:Passive skill table
--
p.passive_skill_table = m_util.misc.invoker_factory(_passive_skill_table, {parentFirst=true})


return p
return p

Revision as of 14:33, 21 December 2025

Module documentation[view] [edit] [history] [purge]


Implements {{Passive skill table}} and {{Simple passive skill list}}.

-------------------------------------------------------------------------------
-- 
--                             Module:Passive skill table
-- 
-- This module implements [[Template:Passive skill table]] and other templates
-- that query and display tables or lists of passive skills.
-------------------------------------------------------------------------------

require('strict')
local m_util = require('Module:Util')

-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Passive skill table')

local m_cargo = use_sandbox and require('Module:Cargo/sandbox') or require('Module:Cargo')

local f_passive_skill_link = require('Module:passive_skill_link').passive_skill_link

-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Passive skill table/config/sandbox') or mw.loadData('Module:Passive skill table/config')

local i18n = cfg.i18n

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

local h = {}

function h.make_order(results, field, args)
    --[[
    Merge duplicates and then create a ordered list.
    ]]
    local values = {}
    local order = {}
    for _, row in ipairs(results) do
        local val = row[field]
        if args.fmt then
            val = args.fmt(row[field])
        end
        -- Can't show results here that don't have a value:
        if val then
            if values[val] == nil then
                values[val] = {row}
                table.insert(order, val)
            else
                table.insert(values[val], row)
            end
        end
    end

    return values, order
end

function h.data_page_links(values, order)
    local out = {}
    for i, key in ipairs(order) do
        local links = {}
        for j, row in ipairs(values[key]) do
            links[#links+1] = string.format(
                '[[%s|&#91;%s&#93;]]',
                row['passive_skills._pageName'],
                j + (i-1) * #values[key]
            )
        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

function h.get_type(passive)
    --[[
    Determine what type of passive skill this passive is.
    ]]
    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
    elseif tonumber(passive['passive_skills.is_atlas_passive']) == 1 then
        key = 'atlas_' .. key
    end

    return key
end

function h.na_or_val(tr, value, func, args)
    --[[

    Parameters
    ----------
    tr :

    value :

    func : function

    args : list of
        Valid keys are:
        * colour

    ]]
    local args = args or {}
    if value == nil or value == '' then
        tr:node(m_util.html.table_cell('na'))
    else
        local raw_value = value
        if func ~= nil then
            value = func(value)
        end

        local td = tr:tag('td')
        td:attr('data-sort-value', raw_value)
        td:wikitext(value)
        if args.colour then
            td:attr('class', 'tc -' .. args.colour)
        end
    end
end

-- Format style for field:
h.fmt = {}
function h.fmt.link(field)
    local fields = m_util.string.split(field, ',')
    for i, v in ipairs(fields) do
        fields[i] = string.format('[[%s]]', v)
    end
    return table.concat(fields, ', ')
end
function h.fmt.passive_skill_link(field)
    local fields = m_util.string.split(field, ',')
    for i, v in ipairs(fields) do
        fields[i] = f_passive_skill_link{id=v}
    end
    return table.concat(fields, ', ')
end
function h.fmt.boolean(field)
    local fields = m_util.string.split(field, ',')
    for i, v in ipairs(fields) do
        if m_util.cast.boolean(v) then
            fields[i] = '<div class="table-yes" data-sort-value=1><span>&#x2713;</span></div>'
        else
            fields[i] = '<div class="table-no" data-sort-value=0><span>&#x2717;</span></div>'
        end
    end
    return table.concat(fields, ', ')
end
function h.fmt.link_passive(field)
    --[[
    Create a link to the passive skill data page from the ID.
    ]]
    local fields = m_util.string.split(field, ',')
    for i, v in ipairs(fields) do
        fields[i] = string.format(
            '[[Passive Skill:%s|%s]]',
            string.gsub(v, '_', '~'),
            v
        )
    end
    return table.concat(fields, ', ')
end

-- Display
h.tbl = {}
h.tbl.display = {}

h.tbl.display.factory = {}
function h.tbl.display.factory.merged_values(args)
    local args = args or {}
    return function (tpl_args, tr, rows, rowinfo)
        local values, order = h.make_order(rows, args.field, args)
        local value = h.data_page_links(values, order)
        h.na_or_val(tr, value, nil, args)
    end
end

-- ----------------------------------------------------------------------------
-- Data mappings
-- ----------------------------------------------------------------------------

local data_map = {}
data_map.passive_skill_table = {
    tables = {
        passive_skill_stats = {
            join='passive_skills._pageID=passive_skill_stats._pageID',
        },
        main_pages = {
            join='passive_skills.id=main_pages.id'
        }
    },
    -- Display data
    {
        order = 1000,
        args = nil,
        header = i18n.passive_table.name,
        fields = {
            -- Optional:
            'passive_skills.is_keystone',
            'passive_skills.is_notable',
            'passive_skills.ascendancy_class',
            'passive_skills.is_atlas_passive',
            'passive_skills.main_page',
            'main_pages._pageName',
            'passive_skills.icon',
            -- 'passive_skills.html',

            -- Required:
            'passive_skills._pageName',
            'passive_skills.name',
        },
        display = function (tpl_args, tr, data)
            local passive = data[1]

            local psl_args = {
                skip_query = true,
                page = passive['passive_skills.main_page']
                    or passive['main_pages._pageName']
                    or passive['passive_skills.name']
                    or passive['passive_skills._pageName'],
                name = passive['passive_skills.name'],
                icon = passive['passive_skills.icon'],
                is_keystone = passive['passive_skills.is_keystone'],
                is_notable = passive['passive_skills.is_notable'],
                ascendancy_class = passive['passive_skills.ascendancy_class'],
                is_atlas_passive = passive['passive_skills.is_atlas_passive'],
            }
            if tpl_args.no_html == nil then
                psl_args.html = passive['passive_skills.html']
            end
            if tpl_args.large then
                psl_args.large = tpl_args.large
            end

            tr
                :tag('td')
                    :attr(
                        'data-sort-value',
                        passive['passive_skills.name'] .. h.get_type(passive)
                    )
                    :attr('style', 'text-align:center;')
                    :wikitext(f_passive_skill_link(psl_args))
                    :done()

        end,
        sort_type = 'text',
        options = {
            [1] = {optional=true},
            [2] = {optional=true},
            [3] = {optional=true},
            [4] = {optional=true},
            [5] = {optional=true},
            [6] = {optional=true},
        },
    },
    {
        order = 0,
        args = {'ascendancy'},
        header = i18n.passive_table.ascendancy_class,
        fields = {'passive_skills.ascendancy_class'},
        display = function (tpl_args, tr, data)
            local passive = data[1]
            h.na_or_val(tr, passive['passive_skills.ascendancy_class'],
                function ()
                    return 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']
                    )
                end
            )
        end,
        sort_type = 'text',
    },
    {
        order = 1001,
        args = 'id',
        header = i18n.passive_table.id,
        fields = {'passive_skills.id'},
        display = h.tbl.display.factory.merged_values{field='passive_skills.id'},
        sort_type = 'text',
    },
    {
        order = 1002,
        args = 'int_id',
        header = i18n.passive_table.int_id,
        fields = {'passive_skills.int_id'},
        display = h.tbl.display.factory.merged_values{field='passive_skills.int_id'},
        sort_type = 'text',
    },
    {
        order = 2000,
        args = {'stat', 'stats', 'stat_text'},
        header = i18n.passive_table.stats,
        fields = {'passive_skills.stat_text'},
        display = h.tbl.display.factory.merged_values{field='passive_skills.stat_text', colour='mod'},
        sort_type = 'text',
    },
    {
        order = 2001,
        args = 'skill_points',
        header = i18n.passive_table.skill_points,
        fields = {'passive_skills.skill_points'},
        display = h.tbl.display.factory.merged_values{field='passive_skills.skill_points'},
        sort_type = 'number',
    },
    {
        order = 2002,
        args = 'weapon_set_points',
        header = i18n.passive_table.weapon_set_points,
        fields = {'passive_skills.weapon_set_points'},
        display = h.tbl.display.factory.merged_values{field='passive_skills.weapon_set_points'},
        sort_type = 'number',
    },
    {
        order = 3000,
        args = 'connections',
        header = i18n.passive_table.connections,
        fields = {'passive_skills.connections'},
        display = h.tbl.display.factory.merged_values{
            field='passive_skills.connections',
            fmt=h.fmt.passive_skill_link,
        },
        sort_type = 'text',
    },
    {
        order = 3001,
        args = 'flavour_text',
        header = i18n.passive_table.flavour_text,
        fields = {'passive_skills.flavour_text'},
        display = h.tbl.display.factory.merged_values{
            field='passive_skills.flavour_text',
            colour='flavour',
        },
        sort_type = 'text',
    },
    {
        order = 3002,
        args = 'reminder_text',
        header = i18n.passive_table.reminder_text,
        fields = {'passive_skills.reminder_text'},
        display = h.tbl.display.factory.merged_values{
            field='passive_skills.reminder_text',
            colour='help',
        },
        sort_type = 'text',
    },
    {
        order = 4000,
        args = 'is_notable',
        header = i18n.passive_table.is_notable,
        fields = {'passive_skills.is_notable'},
        display = h.tbl.display.factory.merged_values{
            field='passive_skills.is_notable',
            fmt=h.fmt.boolean
        },
        sort_type = 'number',
    },
    {
        order = 4001,
        args = 'is_keystone',
        header = i18n.passive_table.is_keystone,
        fields = {'passive_skills.is_keystone'},
        display = h.tbl.display.factory.merged_values{
            field='passive_skills.is_keystone',
            fmt=h.fmt.boolean
        },
        sort_type = 'number',
    },
}

-- ----------------------------------------------------------------------------
-- Main functions
-- ----------------------------------------------------------------------------

local function _passive_skill_table(tpl_args)
    --[[
    Displays a table of passive skills.

    TODO: Why is the groupBy necessary?

    Examples
    --------
    = p.passive_skill_table{
        q_where = 'passive_skills.ascendancy_class = "Gladiator"',
        large=1,
    }
    = p.passive_skill_table{
        q_where='passive_skills.stat_text LIKE "%damage%taken%as%"',
        ascendancy=1,
        stat_text=1,
    }

    ]]
    local t = os.clock()

    -- Check if the correct parameters have been set:
    if tpl_args.q_where == nil then
        return m_util.html.error{msg=i18n.errors.invalid_args .. m_util.misc.add_category(i18n.errors.category)}
    end

    -- Cargo query settings:
    tpl_args.q_groupBy = tpl_args.q_groupBy or 'passive_skills._pageID'
    tpl_args.q_orderBy = tpl_args.q_orderBy or [[
        passive_skills.ascendancy_class IS NULL DESC,
        passive_skills.is_keystone,
        passive_skills.is_notable,
        passive_skills.name
    ]]

    -- Run the query and display the results:
    local out = m_cargo.table_query{
        tpl_args=tpl_args,
        main_table='passive_skills',
        row_unique_fields={'passive_skills.name'},
        data=data_map.passive_skill_table,
        empty_cell=tostring(m_util.html.table_cell('na'))
    }

    mw.logObject({os.clock() - t, tpl_args})

    return out
end


-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------

local p = {}

--
-- Template:Passive skill table
--
p.passive_skill_table = m_util.misc.invoker_factory(_passive_skill_table, {parentFirst=true})

return p