Module:Passive skill: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
(Attach)
(exclude old regional passives from passive skill box query)
 
(One intermediate revision by one other user not shown)
Line 23: Line 23:
         keystone = 'Keystone passive skills',
         keystone = 'Keystone passive skills',
         notable = 'Notable passive skills',
         notable = 'Notable passive skills',
         basic = 'Basic passive skills',
         basic = 'Small passive skills',
         ascendancy_notable = 'Ascendancy notable passive skills',
         ascendancy_notable = 'Ascendancy notable passive skills',
         ascendancy_basic = 'Ascendancy basic passive skills',
         ascendancy_basic = 'Ascendancy small passive skills',
     },
     },
      
      
Line 575: Line 575:
      
      
     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" AND passive_skills.stat_text IS NOT NULL', tpl_args.name)
         tpl_args.q_where = string.format('passive_skills.name="%s" AND passive_skills.stat_text IS NOT NULL AND substring(passive_skills.id, 7, 8) not in ("glennach", "haewark_", "lira_art", "valdos_r", "null_reg", "lex_ejor", "lex_prox", "new_vast", "tirns_en")', 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')

Latest revision as of 03:32, 9 November 2023

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


Lua logo

This module depends on the following other modules:

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

-------------------------------------------------------------------------------
-- 
--                            Module:Passive skill
-- 
-- This module implements Template:Passive skill and Template:Passive skill box
-------------------------------------------------------------------------------

local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')

local f_infocard = require('Module:Infocard')._main

-- ----------------------------------------------------------------------------
-- 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 = 'Small passive skills',
        ascendancy_notable = 'Ascendancy notable passive skills',
        ascendancy_basic = 'Ascendancy small 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, 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, value)
                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, value)
            return string.format('[[%s]]', value)
        end,
    },
    {
        key = 'connections',
        header = i18n.passive_box_table.connections,
        display = function (tpl_args, 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']
    local 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)
    --[[
    Display an introductory text about the passive skill.
    ]]
    local out = {}
    if mw.ustring.find(tpl_args['id'], '_') then
        out[#out+1] = mw.getCurrentFrame():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)
    --[[
    Display the stat box.
    ]]
    local container = mw.html.create('div')
    container
        :attr('class', 'modbox floatright')
        
    -- stat table 
    local 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

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

local function _passive_skill(tpl_args)
    --[[
    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',
    }
    
    ]]

    -- parse 
    m_util.args.from_cargo_map{
        tpl_args=tpl_args,
        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(stat)
    end

    -- Attach to tables
    mw.getCurrentFrame():expandTemplate{title = 'Template:Passive skill/cargo/passive skills/attach'}
    mw.getCurrentFrame():expandTemplate{title = 'Template:Passive skill/cargo/passive skill stats/attach'}
    
    --
    -- 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, 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),
        h.stat_box(tpl_args),
    }
    
    local cats = {
        i18n.cats.data,
    }
    return table.concat(out) .. m_util.misc.add_category(cats)
end

local function _passive_skill_box(tpl_args)
    --[[
    Queries a passive skill and displays it.
    
    Examples
    --------
    = p.passive_skill_box{name='Ghost Reaver'}
    
    ]]
    
    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 AND substring(passive_skills.id, 7, 8) not in ("glennach", "haewark_", "lira_art", "valdos_r", "null_reg", "lex_ejor", "lex_prox", "new_vast", "tirns_en")', 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(
                    {
                        _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

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

local p = {}

-- This way the helper functions can be used in other modules
p.h = h -- Currently used by Module:Blight

-- 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}

--
-- Template:Passive skill
-- 
p.passive_skill = m_util.misc.invoker_factory(_passive_skill, {
    wrappers = 'Template:Passive skill',
})

--
-- Template:Passive skill box
-- 
p.passive_skill_box = m_util.misc.invoker_factory(_passive_skill_box, {
    wrappers = 'Template:Passive skill box',
})

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

return p