Module:Passive skill

From Path of Exile Wiki
Revision as of 11:18, 7 July 2018 by >OmegaK2 (Add a basic infobox to passive skill pages)
Jump to navigation Jump to search
Module documentation[view] [edit] [history] [purge]


Lua logo

This module depends on the following other modules:

Implements {{passive skill}} and {{passive skill box}}.

--
-- Module for bestiary templates
--

local m_util = require('Module:Util')
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',
    },
    
    passive_box = {
        ascendancy = 'Ascendancy',
        keystone = 'Keystone',
        notable = 'Notable Passive Skill',
        passive = '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', '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(unique)',
            required = true,
        },
        int_id = {
            field = 'int_id',
            type = 'Integer(unique)',
            required = true,
        },
        name = {
            field = 'name',
            type = 'String',
        },
        flavour_text = {
            field = 'flavour_text',
            type = 'Text',
        },
        reminder_text = {
            field = 'id',
            type = 'Text',
        },
        buff_id = {
            field = '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 = {'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.skill_points,
        display = function (tpl_args, frame, value)
            local results = m_util.cargo.map_results_to_id{
                field='passive_skills.id',
                results=m_util.cargo.array_query{
                    tables={'passive_skills'},
                    fields={'passive_skills.name', 'passive_skills._pageName'},
                    id_array=value,
                    id_field='passive_skills.id',
                    ingore_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)
    if passive['passive_skills.icon'] == nil then
        return ''
    end
    
    local cls
    if passive['passive_skills.is_keystone'] == "1" then
        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:addClass('passive-icon-container')
    div:addClass(cls)
    div:tag('div')
        :addClass('passive-icon-frame')
        :done()
    div:wikitext(string.format('[[%s]]', passive['passive_skills.icon']))
    
    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

function h.subheader(passive)
    if passive['passive_skills.is_keystone'] == "1" then
        return i18n.passive_box.keystone
    elseif passive['passive_skills.is_notable'] == "1" then
        return i18n.passive_box.notable
    else
        return i18n.passive_box.passive
    end
    
    if passive['passive_skills.ascendancy_class'] ~= nil then
        return i18n.passive_box.ascendancy .. ' ' .. infocard_args.subheader
    end
end
-- ----------------------------------------------------------------------------
-- Page functions
-- ----------------------------------------------------------------------------

local p = {}

p.table_passive_skills = m_util.cargo.declare_factory{data=tables.passive_skills}
p.table_passive_skill_stats = m_util.cargo.declare_factory{data=tables.passive_skill_stats}

function p.passive_skill(frame)
    -- 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_util.cargo.store(frame, stat)
    end
    
    --
    -- Infobox
    --
    local passive = {}
    for _, key in ipairs(display.map_to_property) do
        passive[string.format('%s.%s', tables.passive_skills.table, tables.passive_skills.fields[key].field)] = tpl_args[key]
    end
    
    local infocard_args = {}
    infocard_args.header = tpl_args.name
    infocard_args.subheader = h.subheader(passive)
    
    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
            mw.logObject(data.css)
            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)
    
    --
    --
    --
    
    cats = {
        i18n.cats.data,
    }
    
    return f_infocard(infocard_args) .. m_util.misc.add_category(cats)
end

function p.passive_skill_box(frame)
    -- 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"', tpl_args.name)
    elseif not (tpl_args.q_where and not tpl_args.name) then
        error('q_where or name must be specified')
    end
    
    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._pageName', 
            'passive_skills.stat_text',
            -- TODO: only really need these once, maybe put in extra query
            'passive_skills.flavour_text',
            'passive_skills.stat_text',
            'passive_skills.icon',
            'passive_skills.is_keystone',
            'passive_skills.is_notable',
            'passive_skills.ascendancy_class',
        }, 
        {
            where=tpl_args.q_where,
            orderBy='passive_skills.stat_text',
        }
    )
    
    if #results == 0 then
        error(i18n.errors.no_passives_found)
    end
    
    local stats, stat_order = h.make_stat_order(results)
    
    local passive = results[1]
    
    local infocard_args = {}
    infocard_args.header = tpl_args.name
    infocard_args.subheader = h.subheader(passive)
    
    infocard_args[1] = h.format_passive_icon(passive)
    infocard_args[2] = h.stat_page_links(stat_order, stats)
    
    return f_infocard(infocard_args)
end

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

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 results = m_util.cargo.query(
        {'passive_skills', 'passive_skill_stats'},
        {
            'passive_skills._pageName', 
            'passive_skills.name',
            'passive_skills.stat_text',
            'passive_skills.icon',
            'passive_skills.is_keystone',
            'passive_skills.is_notable',
            'passive_skills.ascendancy_class',
        }, 
        {
            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{
        results=results,
        field='passive_skills.name',
        keep_id_field=true,
    }
    
    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()
    
    for _, rows in pairs(results) do
        local row = rows[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(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
    
    return tostring(tbl)
end

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

return p