Module:Passive skill

-- -- 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 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 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', 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( '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('&#91;%s&#93;', row['passive_skills._pageName'], j)        end out[i] = string.format(' %s %s ', key, table.concat(links, ' ')) end return table.concat(out, ' ') 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 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\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 ', 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', 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 ', 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', 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