Module:Mod: Difference between revisions

From Path of Exile 2 Wiki
Jump to navigation Jump to search
>OmegaK2
(No longer use item link on modifier pages to cut down on query usage)
m (88 revisions imported)
 
(32 intermediate revisions by 7 users not shown)
Line 1: Line 1:
--
-------------------------------------------------------------------------------
-- Module for mod related templates
--  
--
--                            Module:Mod
--
-- This module implements Template:Mod
-------------------------------------------------------------------------------


require('Module:No globals')
local m_util = require('Module:Util')
local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_cargo = require('Module:Cargo')
local game = require('Module:Game')
local f_item_link = require('Module:Item link').item_link


local cargo = mw.ext.cargo
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Mod')


local p = {}
local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or mw.loadData('Module:Game')
 
-- Lazy loading
local f_item_table -- require('Module:Item table').item_table


-- ----------------------------------------------------------------------------
-- The cfg table contains all localisable strings and configuration, to make it
-- Strings
-- easier to port this module to another wiki.
-- ----------------------------------------------------------------------------
local cfg = use_sandbox and mw.loadData('Module:Mod/config/sandbox') or mw.loadData('Module:Mod/config')
-- This section contains strings used by this module.
-- Add new strings here instead of in-code directly, this will help other
-- people to correct spelling mistakes easier and help with translation to
-- other PoE wikis.


local i18n = {
local i18n = cfg.i18n
    args = {
        --
        -- Mod template
        --
       
        -- main
        id = 'id',
        name = 'name',
        mod_group = 'mod_group',
        mod_type = 'mod_type',
        domain = 'domain',
        generation_type = 'generation_type',
        required_level = 'required_level',
        stat_text = 'stat_text',
        granted_buff_id = 'granted_buff_id',
        granted_buff_value = 'granted_buff_value',
        granted_skill = 'granted_skill',
        tags = 'tags',
       
        -- sell price
        sell_price_prefix = 'sell_price',
        item_name = 'name',
        amount = 'amount',
    },
   
    errors = {
        --
        -- Mod template
        --
        sell_price_duplicate_name = 'Do not specify a sell price item name multiple times. Adjust the amount instead.',
        sell_price_missing_argument = 'Both %s and %s must be specified',
    },
}


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- m_utility / Helper functions
-- Helper functions
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------


local h = {}
local h = {}


-- Validate single value properties and set them
-- Lazy loading for Module:Item table
function h.item_table(args)
    if not f_item_table then
        f_item_table = require('Module:Item table').item_table
    end
    return f_item_table(args)
end


h.validate = {}
function h.set_weights(tpl_args, args)
    -- Parses a weighted pair of lists and sets properties
    --
    -- tpl_args: argument table to work with
    -- args:
    --  prefix - input prefix for parsing the arguments from tpl_args
    --  table_map - cargo table map


function h.validate.not_nil (args)
    args = args or {}
     return function (arg)
     for i=1, math.huge do -- repeat until no more weights are found
         if g_args[arg] == nil then
         local prefix = args.prefix .. i
             error(string.format('%s must not be nil', arg))
        local params = {
             tag = string.format('%s_tag', prefix),
            value = string.format('%s_value', prefix),
        }
        local tag = tpl_args[params.tag]
        local value = tpl_args[params.value]
        if tag == nil and value == nil then
            break
         end
         end
    end
        if tag == nil or value == nil then
end
            error(string.format(i18n.errors.invalid_weight, params.tag, params.value))
        end
        value = m_util.cast.number(value, {min = 0})


function h.validate.number (args)
        -- Store to cargo table unless tag = default and value = 0
    return function (tpl_args, frame, value)
        if tag ~= 'default' or value ~= 0 then
        return m_util.cast.number(value, args)
            m_cargo.store({
                _table = args.table_map.table,
                [args.table_map.fields.ordinal.field] = i,
                [args.table_map.fields.tag.field] = tag,
                [args.table_map.fields.value.field] = value,
            })
        end
     end
     end
end
function h.create_header(row)
    local stat = mw.html.create('span')
    local text, nsub = mw.ustring.gsub(row['Has stat text'], '%d+', '?')
    stat
        :attr('class', 'mod-table-header-stat')
        :wikitext(text)
        :done()
       
    local mgroup = mw.html.create('span')
    mgroup
        :attr('class', 'mod-table-header-modgroup')
        :wikitext(row['Has mod group'])
        :done()
       
    local tbl = mw.html.create('table')
    tbl
        :attr('class', 'wikitable mw-collapsible mw-collapsed mod-table')
        :tag('tr')
            :tag('th')
                :attr('class', 'mod-table-header')
                :attr('colspan', g_args.colspan)
                :tag('span')
                    :attr('class', 'mod-table-header-container')
                    :wikitext(tostring(stat) .. tostring(mgroup))
                :done()
            :done()
    return tbl
end
function h.format_mod(tbl, row, tags)
    local tr = tbl:tag('tr')
    tr
        :tag('td')
            :wikitext(string.format('[[%s|%s]]', row[1], row['Has name']))
            :attr('class', 'mod-table-cell-name')
            :done()
        :tag('td')
            :wikitext(row['Has level requirement'])
            :attr('class', 'mod-table-cell-level')
            :done()
        :tag('td')
            :wikitext(row['Has stat text'])
            :attr('class', 'mod-table-cell-stat')
            :done()
        :tag('td')
            :wikitext(table.concat(tags, ', '))
            :attr('class', 'mod-table-cell-tags')
            :done()
end
end


Line 140: Line 86:
     main = {
     main = {
         table = 'mods',
         table = 'mods',
         order = {'id', 'name', 'mod_group', 'mod_type', 'domain', 'generation_type', 'required_level', 'stat_text', 'granted_buff_id', 'granted_buff_value', 'granted_skill', 'tags'},
         display_order = {'id', 'name', 'mod_groups', 'mod_type', 'domain', 'generation_type', 'required_level', 'stat_text', 'granted_buff_id', 'granted_buff_value', 'granted_skill', 'tags', 'tier_text'},
         parse_order = {'id', 'name', 'mod_group', 'mod_type', 'domain', 'generation_type', 'required_level', 'stat_text', 'stat_text_raw', 'granted_buff_id', 'granted_buff_value', 'granted_skill', 'tags'},
         order = {'id', 'name', 'mod_groups', 'mod_type', 'domain', 'generation_type', 'required_level', 'stat_text', 'stat_text_raw', 'granted_buff_id', 'granted_buff_value', 'granted_skill', 'tags', 'tier_text'},
         fields = {
         fields = {
             id = {
             id = {
                 name = i18n.args.id,
                 name = 'id',
                 field = i18n.args.id,
                 field = 'id',
                 type = 'String',
                 type = 'String',
                 wikitext = 'Mod Id',
                 wikitext = i18n.data_sheet.id,
                func = function (tpl_args, value)
                    -- Validate that the id is unique
                    local results = m_cargo.query(
                        {'mods'},
                        {'mods._pageName'},
                        {
                            where = string.format(
                                'mods.id = "%s" AND mods._pageName != "%s"',
                                value,
                                m_cargo.addslashes(mw.title.getCurrentTitle().prefixedText)
                            )
                        }
                    )
                    if #results > 0 then
                        error(string.format(i18n.errors.duplicate_mod_id, results[1]['mods._pageName']))
                    end
                    return value
                end
             },
             },
             name = {
             name = {
                 name = i18n.args.name,
                 name = 'name',
                 field = i18n.args.name,
                 field = 'name',
                 type = 'String',
                 type = 'String',
                 wikitext = 'Name',
                 wikitext = i18n.data_sheet.name,
             },
             },
             mod_group = {
             mod_groups = {
                 name = i18n.args.mod_group,
                 name = 'mod_groups',
                 field = i18n.args.mod_group,
                 field = 'mod_groups',
                 type = 'String',
                 type = 'List (,) of String',
                 wikitext = 'Group',
                 wikitext = i18n.data_sheet.mod_groups,
                display = function (value)
                    return table.concat(value, ', '
                end,
                default = {},
             },
             },
             mod_type = {
             mod_type = {
                 name = i18n.args.mod_type,
                 name = 'mod_type',
                 field = i18n.args.mod_type,
                 field = 'mod_type',
                 type = 'String',
                 type = 'String',
                 wikitext = 'Mod type',
                 wikitext = i18n.data_sheet.mod_type,
             },
             },
             domain = {
             domain = {
                 name = i18n.args.domain,
                 name = 'domain',
                 field = i18n.args.domain,
                 field = 'domain',
                 type = 'Integer',
                 type = 'Integer',
                func = h.validate.number{min=1, max=15},
                 wikitext = 'Mod domain',
                 wikitext = 'Mod domain',
                 display = function (value)
                 display = function (value)
                     return game.constants.mod.domains[value]['short_upper'] .. ' (Id: ' .. value .. ')'
                     return string.format(i18n.data_sheet.domain_fmt, m_game.constants.mod.domains[value]['short_upper'], value)
                 end,
                 end,
             },
             },
             generation_type = {
             generation_type = {
                 name = i18n.args.generation_type,
                 name = 'generation_type',
                 field = i18n.args.generation_type,
                 field = 'generation_type',
                 type = 'Integer',
                 type = 'Integer',
                 func = h.validate.number{min=1, max=12},
                 wikitext = i18n.data_sheet.generation_type,
                wikitext = 'Generation type',
                 display = function (value)
                 display = function (value)
                     return game.constants.mod.generation_types[value]['short_upper'] .. ' (Id: ' .. value .. ')'
                     return string.format(i18n.data_sheet.generation_type_fmt, m_game.constants.mod.generation_types[value]['short_upper'], value)
                 end,
                 end,
             },
             },
             required_level = {
             required_level = {
                 name = i18n.args.required_level,
                 name = 'required_level',
                 field = i18n.args.required_level,
                 field = 'required_level',
                 type = 'Integer',
                 type = 'Integer',
                 func = h.validate.number{min=0, max=100},
                 wikitext = i18n.data_sheet.required_level,
                wikitext = 'Req. level',
             },
             },
             stat_text = {
             stat_text = {
                 name = i18n.args.stat_text,
                 name = 'stat_text',
                 field = i18n.args.stat_text,
                 field = 'stat_text',
                 type = 'Text',
                 type = 'Text',
                 wikitext = 'Effect',
                 wikitext = i18n.data_sheet.stat_text,
             },
             },
             stat_text_raw = {
             stat_text_raw = {
Line 204: Line 169:
                 field = 'stat_text_raw',
                 field = 'stat_text_raw',
                 type = 'Text',
                 type = 'Text',
                 func = function(tpl_args, frame)
                 func = function (tpl_args, value)
                     tpl_args.stat_text_raw = string.gsub(
                     if tpl_args.stat_text then
                         -- [[x]] -> x
                         -- Strip wikilinks and html, but keep any line break tags
                         string.gsub(
                         value = m_util.string.strip_wikilinks(tpl_args.stat_text)
                            tpl_args.stat_text, '%[%[([^%]|]+)%]%]', '%1'
                        value = mw.ustring.gsub(value, '<br */?>', '')
                         ),
                         value = m_util.string.strip_html(value)
                        -- [[x|y]] -> y
                         value = mw.ustring.gsub(value, '', '<br>')
                         '%[%[[^|]+|([^%]|]+)%]%]', '%1'
                     end
                     )
                     return value
                     return tpl_args.stat_text_raw
                 end
                 end
             },
             },
             granted_buff_id = {
             granted_buff_id = {
                 name = i18n.args.granted_buff_id,
                 name = 'granted_buff_id',
                 field = i18n.args.granted_buff_id,
                 field = 'granted_buff_id',
                 type = 'String',
                 type = 'String',
                 wikitext = 'Granted Buff Id',
                 wikitext = i18n.data_sheet.granted_buff_id,
             },
             },
             granted_buff_value = {
             granted_buff_value = {
                 name = i18n.args.granted_buff_value,
                 name = 'granted_buff_value',
                 field = i18n.args.granted_buff_value,
                 field = 'granted_buff_value',
                 type = 'Integer',
                 type = 'Integer',
                 wikitext = 'Granted Buff Value',
                 wikitext = i18n.data_sheet.granted_buff_value,
             },
             },
             granted_skill = {
             granted_skill = {
                 name = i18n.args.granted_skill,
                 name = 'granted_skill',
                 field = i18n.args.granted_skill,
                 field = 'granted_skill',
                 type = 'String',
                 type = 'String',
                 wikitext = 'Granted Skill',
                 wikitext = i18n.data_sheet.granted_skill,
             },
             },
             tags = {
             tags = {
                 name = i18n.args.tags,
                 name = 'tags',
                 field = i18n.args.tags,
                 field = 'tags',
                 type = 'List (,) of String',
                 type = 'List (,) of String',
                 wikitext = 'Tags',  
                 wikitext = 'Tags',  
                 func = function (tpl_args, frame, value)
                 display = function (value)
                     if value == nil then
                     return table.concat(value, ', ')
                        return {}
                    else
                        return m_util.string.split(value, ', ')
                    end
                 end,
                 end,
                 func_smw = function(tpl_args, frame)
                 default = {},
                    return table.concat(tpl_args.tags, ';'
            },
                 end,  
            tier_text = {
                 func_cargo = function(tpl_args, frame)
                name = 'tier_text',
                    return table.concat(tpl_args.tags, ','
                field = 'tier_text',
                 end,
                type = 'Text',
                 display = function(value)
                wikitext = i18n.data_sheet.tier_text,
                    return table.concat(value, ', '
            },
                 end,                 
        },
    },
    mod_stats = {
        table = 'mod_stats',
        fields = {
            id = {
                field = 'id',
                type = 'String',
            },
            min = {
                 field = 'min',
                 type = 'Integer',
            },
            max = {
                field = 'max',
                type = 'Integer',
            },
        },
    },
    mod_spawn_weights = {
        table = 'mod_spawn_weights',
        fields = {
            ordinal = {
                field = 'ordinal',
                type = 'Integer',
            },
            tag = {
                field = 'tag',
                 type = 'String',
            },
            value = {
                 field = 'value',
                type = 'Integer',
            },
        },
    },
    mod_generation_weights = {
        table = 'mod_generation_weights',
        fields = {
            ordinal = {
                field = 'ordinal',
                type = 'Integer',
            },
            tag = {
                field = 'tag',
                type = 'String',
            },
            value = {
                 field = 'value',
                 type = 'Integer',
             },
             },
         },
         },
Line 263: Line 272:
         fields = {
         fields = {
             name = {
             name = {
                 name = i18n.args.item_name,
                 name = 'name',
                 field = i18n.args.item_name,
                 field = 'name',
                 type = 'String',
                 type = 'String',
                 func = function (value) return value end,
                 func = function (value) return value end,
             },
             },
             amount = {
             amount = {
                 name = i18n.args.amount,
                 name = 'amount',
                 field = i18n.args.amount,
                 field = 'amount',
                 type = 'Integer',
                 type = 'Integer',
                 func = tonumber,
                 func = tonumber,
Line 278: Line 287:
}
}


p.table_main = m_util.cargo.declare_factory{data=mod_map.main}
-- ----------------------------------------------------------------------------
p.table_mod_sell_prices = m_util.cargo.declare_factory{data=mod_map.mod_sell_prices}
-- Main functions
 
-- ----------------------------------------------------------------------------
function p.table_mod_stats(frame)
    m_util.cargo.declare(frame, {
        _table = 'mod_stats',
        id = 'String',
        min = 'Integer',
        max = 'Integer',
    })
end
 


-- p.mod{id = "LocalIncreasedPhysicalDamagePercentUniqueOneHandSword2", name = "", mod_group = "LocalPhysicalDamagePercent", domain = "1", generation_type = "3", required_level = "1", mod_type = "LocalPhysicalDamagePercent", stat_text = "150% increased Physical Damage", stat1_id = "local_physical_damage_+%", stat1_min = "150", stat1_max = "150"}
local function _mod(tpl_args)
function p.mod(frame)
    -- p.mod{id = "LocalIncreasedPhysicalDamagePercentUniqueOneHandSword2", name = "", mod_groups = "LocalPhysicalDamagePercent, Dexterity", domain = "1", generation_type = "3", required_level = "1", mod_type = "LocalPhysicalDamagePercent", stat_text = "150% increased Physical Damage", stat1_id = "local_physical_damage_+%", stat1_min = "150", stat1_max = "150"}
    -- Get args
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
      
      
     --
     --
     -- Validation & semantic properties
     -- Validate and store
     --
     --
   
 
     -- Validate single value properties and set them
     -- Validate single value properties and set them
      
     m_cargo.store_mapped_args{
    local cargo_data = {
        tpl_args = tpl_args,
         _table = mod_map.main.table,
         table_map = mod_map.main,
     }
     }
      
      
    for _, key in pairs(mod_map.main.parse_order) do
     -- Validate and store stats
        data = mod_map.main.fields[key]
     m_util.args.stats(tpl_args)
        local value
        if data.func ~= nil then
            if data.name then
                value = data.func(tpl_args, frame, tpl_args[data.name])
            else
                value = data.func(tpl_args, frame)
            end
        else
            value = tpl_args[data.name]
        end
       
        tpl_args[key] = value
 
        if data.field ~= nil then
            if data.func_cargo then
                cargo_data[data.field] = data.func_cargo(tpl_args, frame)
            else
                cargo_data[data.field] = value
            end
        end
    end
 
    m_util.cargo.store(frame, cargo_data)
   
     -- Validate % set the stat subobjects
     m_util.args.stats(tpl_args, {frame=frame})
     for _, stat_data in pairs(tpl_args.stats) do
     for _, stat_data in pairs(tpl_args.stats) do
         m_util.cargo.store(frame, {
         m_cargo.store({
             _table = 'mod_stats',  
             _table = 'mod_stats',  
             id = stat_data.id,
             id = stat_data.id,
Line 346: Line 315:
     end
     end
      
      
     -- Validate & set spawn weight subobjects
     -- Validate and store spawn weights
     m_util.args.spawn_weight_list(tpl_args, {
     h.set_weights(tpl_args, {
         frame=frame,  
         prefix = 'spawn_weight',
        table_map = mod_map.mod_spawn_weights
     })
     })
      
      
     -- Validate & set generation weight subobjects
     -- Validate and store generation weights
     m_util.args.generation_weight_list(tpl_args, {
     h.set_weights(tpl_args, {
         frame=frame,  
         prefix = 'generation_weight',
        table_map = mod_map.mod_generation_weights
     })
     })
      
      
     -- Validate & set mod sell values
     -- Validate and store mod sell values
     i = 0
     local i = 0
     local names = {}
     local names = {}
     local sell_prices = {}
     local sell_prices = {}
Line 364: Line 335:
          
          
         local id = {}
         local id = {}
         value = {}
         local value = {}
         for key, data in pairs(mod_map.mod_sell_prices.fields) do
         for key, data in pairs(mod_map.mod_sell_prices.fields) do
             id[key] = string.format('%s%s_%s', i18n.args.sell_price_prefix, i, data.name)
             id[key] = string.format('%s%s_%s', 'sell_price', i, data.name)
             value[key] = data.func(tpl_args[id[key]])
             value[key] = data.func(tpl_args[id[key]])
         end
         end
Line 385: Line 356:
                 cargo_data[data.field] = value[key]
                 cargo_data[data.field] = value[key]
             end
             end
             m_util.cargo.store(frame, cargo_data)
             m_cargo.store(cargo_data)
              
              
             sell_prices[#sell_prices+1] = value
             sell_prices[#sell_prices+1] = value
Line 393: Line 364:
          
          
     until value == nil
     until value == nil
    -- Attach to tables
    mw.getCurrentFrame():expandTemplate{title = 'Template:Mod/cargo/mods/attach'}
    mw.getCurrentFrame():expandTemplate{title = 'Template:Mod/cargo/mod stats/attach'}
    mw.getCurrentFrame():expandTemplate{title = 'Template:Mod/cargo/mod spawn weights/attach'}
    mw.getCurrentFrame():expandTemplate{title = 'Template:Mod/cargo/mod generation weights/attach'}
    mw.getCurrentFrame():expandTemplate{title = 'Template:Mod/cargo/mod sell prices/attach'}
      
      
     --
     --
Line 399: Line 377:
      
      
     local container = mw.html.create('div')
     local container = mw.html.create('div')
    container
         :addClass('modbox')
         :attr('class', 'modbox')
      
      
     -- core stats
     -- core stats
      
      
     local tbl = container:tag('table')
     local tbl = container:tag('table')
    tbl
         :addClass('wikitable')
         :attr('class', 'wikitable')
      
      
     for _, key in ipairs(mod_map.main.order) do
     for _, key in ipairs(mod_map.main.display_order) do
         local data = mod_map.main.fields[key]
         local data = mod_map.main.fields[key]
         local text
         local text = tpl_args[key]
         if data.display == nil then
         if type(data.display) == 'function' then
            text = tpl_args[key]
             text = data.display(text)
        else
             text = data.display(tpl_args[key])
         end
         end
       
         tbl
         tbl
             :tag('tr')
             :tag('tr')
Line 428: Line 401:
             :done()
             :done()
     end
     end
   
    tbl
        :tag('tr')
            :tag('th')
                :wikitext('[[Property:Has tag|Tags]]')
                :done()
            :tag('td')
                :wikitext(table.concat(tpl_args['tags'], ', '))
                :done()
            :done()
        :done()
      
      
     -- stat table
     -- stat table
Line 444: Line 406:
     tbl = container:tag('table')
     tbl = container:tag('table')
     tbl
     tbl
         :attr('class', 'wikitable sortable')
         :addClass('wikitable sortable')
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :attr('colspan', 4)
                 :attr('colspan', 4)
                 :wikitext('Stats')
                 :wikitext(i18n.data_sheet.stats)
                 :done()
                 :done()
             :done()
             :done()
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Is stat number|#]]')
                 :wikitext(i18n.data_sheet.ordinal)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Has stat id|Stat Id]]')
                 :wikitext(i18n.data_sheet.stat_id)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Has minimum stat value|Minimum]]')
                 :wikitext(i18n.data_sheet.min)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Has maximum stat value|Maximum]]')
                 :wikitext(i18n.data_sheet.max)
                 :done()
                 :done()
             :done()
             :done()
Line 498: Line 460:
     tbl = container:tag('table')
     tbl = container:tag('table')
     tbl
     tbl
         :attr('class', 'wikitable sortable')
         :addClass('wikitable sortable')
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :attr('colspan', 3)
                 :attr('colspan', 3)
                 :wikitext('Spawn Weights')
                 :wikitext(i18n.data_sheet.spawn_weights)
                 :done()
                 :done()
             :done()
             :done()
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Is tag number|#]]')
                 :wikitext(i18n.data_sheet.ordinal)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Has tag|Tag]]')
                 :wikitext(i18n.data_sheet.tag)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Has spawn weight|Weight]]')
                 :wikitext(i18n.data_sheet.weight)
                 :done()
                 :done()
             :done()
             :done()
Line 519: Line 481:
          
          
     i = 0
     i = 0
     value = nil
     local value = nil
     repeat
     repeat
         i = i + 1
         i = i + 1
Line 548: Line 510:
     tbl = container:tag('table')
     tbl = container:tag('table')
     tbl
     tbl
         :attr('class', 'wikitable sortable')
         :addClass('wikitable sortable')
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :attr('colspan', 3)
                 :attr('colspan', 3)
                 :wikitext('Generation Weights')
                 :wikitext(i18n.data_sheet.generation_weights)
                 :done()
                 :done()
             :done()
             :done()
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Is tag number|#]]')
                 :wikitext(i18n.data_sheet.ordinal)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Has tag|Tag]]')
                 :wikitext(i18n.data_sheet.tag)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Has generation weight|Weight]]')
                 :wikitext(i18n.data_sheet.weight)
                 :done()
                 :done()
             :done()
             :done()
Line 597: Line 559:
     tbl = container:tag('table')
     tbl = container:tag('table')
     tbl
     tbl
         :attr('class', 'wikitable sortable')
         :addClass('wikitable sortable')
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :attr('colspan', 2)
                 :attr('colspan', 2)
                 :wikitext('Modifier sell price')
                 :wikitext(i18n.data_sheet.sell_price)
                 :done()
                 :done()
             :done()
             :done()
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Has sell price amount|#]]')
                 :wikitext(i18n.data_sheet.ordinal)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext('[[Property:Has sell price item name|Item]]')
                 :wikitext(i18n.data_sheet.item)
                 :done()
                 :done()
             :done()
             :done()
Line 628: Line 590:
     -- Generic messages on the page
     -- Generic messages on the page
      
      
     out = {}
     local out = {}
      
      
     if mw.ustring.find(tpl_args['id'], '_') then
     if mw.ustring.find(tpl_args['id'], '_') then
         out[#out+1] = frame:expandTemplate{ title = 'Incorrect title', args = { title=tpl_args['id'] } } .. '\n\n\n'
         out[#out+1] = mw.getCurrentFrame():expandTemplate{ title = 'Incorrect title', args = { title=tpl_args['id'] } } .. '\n\n\n'
     end
     end
      
      
     if tpl_args['name'] then
     if tpl_args['name'] then
         out[#out+1] = string.format("'''%s''' is the internal id of modifier '''%s'''.\n", tpl_args['id'], tpl_args['name'])
         out[#out+1] = string.format(i18n.sections.intro_named_id, tpl_args['id'], tpl_args['name'])
     else
     else
         out[#out+1] = string.format("'''%s''' is the internal id of an unnamed modifier.\n", tpl_args['id'], tpl_args['name'])
         out[#out+1] = string.format(i18n.sections.intro_unnamed_id, tpl_args['id'])
     end
     end
   
    -- Categories
   
    cats = {'Mods'}
   
    -- Done -> output
   
    return tostring(container) .. m_util.misc.add_category(cats) .. '\n' .. table.concat(out)
end


--
     -- Item usage
-- Template: SMW query mods
     local items = m_cargo.query(
--
         {'item_mods'},
 
         {'item_mods._pageName=page'},
function p.query_mods(frame)
         {
     -- Args
             where = string.format(
    g_args = getArgs(frame, {
                 'item_mods.id = "%s"',
        parentFirst = true
                 tpl_args['id']
    })
             )
    g_frame = m_util.misc.get_frame(frame)
   
    g_args.colspan = 4
   
     local conditions = {}
    conditions[#conditions+1] = 'concept'
    if g_args.tag then
        conditions[#conditions+1] = string.format('[[Has subobject::<q>[[-Has subobject::+]] [[Has spawn weight::>>0]] [[Has tag::%s]]</q>]]', g_args.tag)
    end
   
    g_args.header_level = g_args.header_level or 2
   
    -- Fields
    local fields = {}
    fields[#fields+1] = '?Is mod'
    fields[#fields+1] = '?Has name'
    fields[#fields+1] = '?Has level requirement'
    fields[#fields+1] = '?Has mod group'
    fields[#fields+1] = '?Has stat text'
    -- parameters
    local parameters = {}
    parameters.sort = 'Has mod group, '
    parameters.limit = 1000 -- lets see
   
    local data = {}
    data.header = {
        prefix = 'Prefix',
         suffix = 'Suffix',
    }
   
    local out = {}
    for _, v in ipairs({'prefix', 'suffix'}) do
        out[#out+1] = string.format('<h%i>%s</h%i>', g_args.header_level, data.header[v], g_args.header_level)
        conditions[1] = string.format('[[Concept:Spawnable named %s item mods]]', v)
          
        local query
        local results
        --
        -- Query tags
        --
        query = {}
        query[#query+1] = string.format('[[-Has subobject::<q>%s</q>]]', table.concat(conditions, ' '))
        query[#query+1] = '[[Has tag::+]]'
        query[#query+1] = '[[Has spawn weight::+]]'
        --query[#query+1] = '[[Has spawn weight::>>0]]'
        query[#query+1] = '?Has tag'
        query[#query+1] = '?Has spawn weight#' -- need native number
        query.limit = 1000
        query.offset = 0
        -- Tag order is very important
        query.sort = ', Is tag number'
       
        local tags = {}
        -- this works because lua only considers nil to be false >_>
        while query.offset do
            results = m_util.smw.query(query, g_frame)
            query.offset = query.offset + #results
            -- terminates the while if enough reuslts have been fetched
            if query.offset % 1000 ~= 0 then
                query.offset = nil
            end
           
            for _, row in ipairs(results) do
                local page, _ = string.gsub(row[1], '#_[%x]+', '')
                if tags[page] == nil then
                    tags[page] = {}
                end
               
                local text
                if tonumber(row['Has spawn weight']) > 0 then
                    text = '[[File:Yes.png|yes|link=]]'
                else
                    text = '[[File:No.png|no|link=]]'
                end
               
                tags[page][#tags[page]+1] = string.format('%s %s', row['Has tag'], text)
            end
        end
       
        --
        -- Query mods
        --
         query = {}
        for _, v in ipairs(conditions) do
            query[#query+1] = v
        end
        for _, v in ipairs(fields) do
            query[#query+1] = v
        end
        for k, v in pairs(parameters) do
            query[k] = v
        end
       
        results = m_util.smw.query(query, g_frame)
       
        local last = ''
        local tbl = ''
        for _, row in ipairs(results) do
             local current = string.gsub(row['Is mod'], '%d+.*', '%%d.*')
            if string.match(last, current) then
                h.format_mod(tbl, row, tags[row[1]])
            else
                 out[#out+1] = tostring(tbl)
                tbl = h.create_header(row)
                h.format_mod(tbl, row, tags[row[1]])
            end
            last = row['Is mod']
        end
       
        -- add the last table
        out[#out+1] = tostring(tbl)
    end
   
    return table.concat(out, '')
end
 
--
-- Template: SMW mod table
--
 
-- =p.mod_list{'Strength1', 'shitty name', 'test', 'test2', userparam='extra_rows=2, show_jewels=1'}
-- =p.mod_list{'ColdCritMultiplier', 'shitty name', 'test', 'test2', userparam='extra_rows=2, show_jewels=1'}
-- =p.mod_list{'MapMultipleExilesMap2Tier', 'asdasda', 'Area yields 15% more Items<br>8% increased Rarity of Items found in this Area<br>Area is inhabited by 2 additional Rogue Exiles<br>Extra monsters ignore rarity bias (Hidden)<br>+14% Monster pack size', userparam='extra_rows=1, type=map, effect_rowid=2'}
 
function p.mod_list(frame)
    local types = {'map', 'jewel'}
   
    -- Args
    g_args = getArgs(frame, {
        parentFirst = true
    })
    g_frame = m_util.misc.get_frame(frame)
   
    --
    local args = m_util.string.split_args(g_args.userparam, {sep=', '})
    g_args.userparam = args
   
    args.extra_rows = (tonumber(args.extra_rows) or 0)
    if args.show_tags == nil then
        args.show_tags = true
    else
        args.show_tags = m_util.cast.boolean(args.show_tags)
    end
    args.effect_rowid = (tonumber(args.effect_rowid) or 0) + 1
 
    tr = mw.html.create('tr')
    tr
        :tag('td')
            :attr('data-sort-value', g_args[2] or g_args[1])
            :wikitext(string.format('[[%s|%s]]', g_args[1], g_args[2] or g_args[1]))
            :done()
   
    local i = 2
    local row_max = i + args.extra_rows
    local text
    while i < row_max do
        i = i + 1
        text = g_args[i] or ''
        text = table.concat(mw.text.split(text, ';', true), '<br>')
       
        if args.type == 'map' and i == args.effect_rowid then
            text = mw.text.split(text, '<br>', true)
           
            local map = {
                '%d+%% increased Quantity of Items found in this Area',
                '%d+%% increased Rarity of Items found in this Area',
                '%+%d+%% Monster pack size',
            }
            out = {}
           
            local valid
            for k, v in pairs(text) do
                valid = true
                for _, pattern in ipairs(map) do
                    if mw.ustring.find(v, pattern) ~= nil then
                        valid = false
                        break
                    end
                end
               
                if valid then
                    table.insert(out, v)
                end
            end
           
            text = table.concat(out, '<br>')
        end
           
        tr
            :tag('td')
                :wikitext(text)
                 :done()
    end
   
   
    local query
    local result
   
    if args.type == 'map' then
        query = {
            string.format('[[-Has subobject::%s]]', g_args[1]),
            '[[Has stat id::+]]',
             '?Has stat id',
            '?Has minimum stat value',
            '?Has maximum stat value',
         }
         }
       
    )
        result = m_util.smw.query(query, g_frame)
    if #items > 0 then
       
         local html = mw.html.create()
        local stat_map = {
             :tag('h2')
            ['map_item_drop_quantity_+%'] = {disp=0, sort=0},
                 :wikitext(i18n.sections.items)
            ['map_item_drop_rarity_+%'] = {disp=0, sort=0},
            ['map_pack_size_+%'] = {disp=0, sort=0},
        }
       
        local stat
        for _, row in ipairs(result) do
            stat = stat_map[row['Has stat id']]
            if stat ~= nil then
                stat.sort = (row['Has minimum stat value'] + row['Has maximum stat value']) / 2
                if row['Has minimum stat value'] ~= row['Has minimum stat value'] then
                    stat.disp = string.format('(%s%-%s)', row['Has minimum stat value'], row['Has maximum stat value'])
                else
                    stat.disp = row['Has minimum stat value']
                end
            end
        end
       
        for _, k in ipairs({'map_item_drop_quantity_+%', 'map_item_drop_rarity_+%', 'map_pack_size_+%'}) do
            stat = stat_map[k]
            tr
                :tag('td')
                    :attr('data-sort-value', stat.sort)
                    :wikitext(stat.disp)
                    :done()
                :done()
         end
    end
   
    local tags
    if args.show_tags or args.type == 'jewel' then
        query = {
            string.format('[[-Has subobject::%s]]', g_args[1]),
            '[[Has tag::+]]',
            '?Has tag',
            '?Has spawn weight',
            sort='Is tag number',
        }
 
        tags = {}
        result = m_util.smw.query(query, g_frame)
    end
   
    if args.type == 'jewel' then
        local jewels = {
            dex=0,
            str=0,
            int=0,
            pris=0,
        }
       
        local cast_tbl = {
            not_dex={'str', 'int'},
            not_int={'str', 'dex'},
            not_str={'int', 'dex'},
            default={'str','int','dex','pris'},
        }
       
        local i = #result
        local row
        local cast
        while i > 0 do
            row = result[i]
            cast = cast_tbl[row['Has tag']]
            if cast ~= nil then
                for _, k in ipairs(cast) do
                    jewels[k] = row['Has spawn weight']
                end
            end
           
            i = i - 1
        end
       
        tr
             :tag('td')
                :attr('class', 'table-cell-dex')
                 :wikitext(jewels.dex)
                 :done()
                 :done()
             :tag('td')
             :tag('p')
                :attr('class', 'table-cell-int')
                 :wikitext(i18n.sections.used_by_items)
                 :wikitext(jewels.int)
                 :done()
                 :done()
            :tag('td')
        out[#out+1] = tostring(html)
                :attr('class', 'table-cell-str')
         out[#out+1] = h.item_table{
                :wikitext(jewels.str)
             q_tables = 'items',
                :done()
             q_where = string.format(
            :tag('td')
                'items._pageName IN ("%s")',
                :attr('class', 'table-cell-prismatic')
                table.concat(m_util.table.column(items, 'page'), '","')
                :wikitext(jewels.pris)
            ),
                :done()
            q_orderBy = 'items.name ASC',
            :done()
    end
   
    if args.show_tags then
         for _, row in ipairs(result) do
            tags[#tags+1] = string.format('%s %s', row['Has tag'], row['Has spawn weight'])
        end
       
        tr
             :tag('td')
                :wikitext(table.concat(tags, '<br>'))
                :done()
             :done()
    end
   
    return tostring(tr)
end
 
function p.item_sell_price(frame)
    -- Query and sum the vendor prices for an item.
    -- Unidentified items won't currently show the correct vendor price. Not sure how that is specified, nor is it used at all.
    -- Expanding {{il}} seems to give a nil /n the first time a new command is run. Doesn't always happen.
   
    -- = p.item_sell_price{page="Voideye"}
    -- = p.item_sell_price{page="Pyre"}
    -- = p.item_sell_price{page="Vessel of Vinktar (Lightning Penetration)"}
   
    -- Args
    local g_args = getArgs(frame, {
        parentFirst = true
    })
    local g_frame = m_util.misc.get_frame(frame)
 
    -- Only the explicit modifiers are counted when vendors calculates the price.
    local condition = string.format('[[%s]]', g_args['page'])
    local query_item_mods = {
        condition,
        '?Has explicit mod ids',
        '?Has rarity',
    }
    local results_query_item_mods = m_util.smw.query(query_item_mods, g_frame)
    local item_mods = m_util.string.split(results_query_item_mods[1]['Has explicit mod ids'], '<MANY>')  
   
    -- If the item has a Normal rarity then the sell price would be a fixed price.
    if results_query_item_mods[1]['Has rarity'] == 'Normal' then
        local amount_normal = 1
        local currency_normal = 'Scroll Fragment'
        return string.format('%s %s', amount_normal, g_frame:expandTemplate{title = 'il', args = {currency_normal, currency_normal .. 's'}})
       
        -- return string.format('%s %s', amount_normal, currency_normal)
    end
   
    local mods_sell_price = {}
    for _, modid in ipairs(item_mods) do
        local query_mod_page = {
            string.format('[[Is mod::%s]]', modid),
         }
         }
        local mod_page = m_util.smw.query(query_mod_page, g_frame)
       
        local query_mod_sell_price = {
            string.format('[[-Has subobject::%s]]', mod_page[1][1]),
            '?Has sell price amount#',
            '?Has sell price item name',
        }
        local results = m_util.smw.query(query_mod_sell_price, g_frame)
       
        for _, k in ipairs(results) do
            if k['Has sell price amount'] ~= '' then
                if mods_sell_price[k['Has sell price item name']] == nil then
                    mods_sell_price[k['Has sell price item name']] = k['Has sell price amount']
                else
                    mods_sell_price[k['Has sell price item name']] = k['Has sell price amount'] + mods_sell_price[k['Has sell price item name']]
                end
            end
        end
     end
     end
      
      
     local out = {}
     -- Categories
    for currency, amount in pairs(mods_sell_price) do
        out[#out+1] = string.format('%s %s', amount, g_frame:expandTemplate{title = 'il', args = {currency, currency .. 's'}})
    end
      
      
     return table.concat(out, ', ')
     local cats = {i18n.categories.mods}
end   
      
      
 
     -- Done -> output
function p.find_mod_domain(input)
     -- Find the mod domain based on the item class.
      
      
     local out = input
     return tostring(container) .. m_util.misc.add_category(cats) .. '\n' .. table.concat(out)
    local mod_domains = game.constants.mod.domains
   
    for i,_ in ipairs(out) do
        out[i]['Has mod domain'] = 1
        for j, row in pairs(mod_domains) do
            if out[i]['Has item class']:gsub('Map', 'Area'):match(mod_domains[j]['short_upper']) then -- This may need updating if an area item class doesn't have 'Map' in the string, or if the mod domain descriptions doesn't match the item class.
                out[i]['Has mod domain'] = j
            end
        end
       
        out[i]['Has mod domain text'] = mod_domains[out[i]['Has mod domain']]['short_lower']
    end
 
    return out
end
end


function p.get_item_tags(frame)
-- ----------------------------------------------------------------------------
    -- This function queries for the tags of a specific item.
-- Exported functions
   
-- ----------------------------------------------------------------------------
    -- Args
    local g_args = getArgs(frame, {
        parentFirst = true
    })
    local g_frame = m_util.misc.get_frame(frame)
   
    local item_name = g_args[1]
   
    local query = {
        string.format('[[Has name::%s]]', item_name),
        '?Has tags',
        '?Has base strength requirement',
        '?Has base intelligence requirement',
        '?Has base dexterity requirement',
        '?Has item class'
    }
    local results = m_util.smw.query(query, g_frame)
   
    for i,_ in ipairs(results) do
        results[i]['Has tags'] = results[i]['Has tags']:gsub('(<MANY>)', ', ') -- Remove unnecessary symbols.
    end
   
    results = p.find_mod_domain(results)
   
    return results
end


function p.header(str)
local p = {}
    -- This function replace specific numbers with a generic #.
   
    local s = table.concat(m_util.string.split(str, '%(%d+%.*%d*%-%d+%.*%d*%)'), '#')
    s = table.concat(m_util.string.split(s, '%d+%.*%d*'), '#')
    s = table.concat(m_util.string.split(s, '<br>'), ', ')
 
  return s
end
 
function p.get_spawn_chance(frame)
    -- Calculates the spawn chance of a set of mods that all have a spawn weight.
   
    -- Args
    local g_args = getArgs(frame, {
        parentFirst = true
    })
    local g_frame = m_util.misc.get_frame(frame)
   
    local tbl = g_args['tbl']
    local chance_multiplier = tonumber(g_args['chance_multiplier']) or 1 -- Probabillities affecting the result besides the spawn weight.
   
    local N = 0
    for i,_ in ipairs(tbl) do
        N = N + tbl[i]['Has spawn weight'] -- Total number of outcomes.
    end
   
    for i,_ in ipairs(tbl) do
        local n = tbl[i]['Has spawn weight'] -- Number of ways it can happen.
       
        tbl[i]['Has spawn chance'] = string.format("%0.2f%%", n/N * chance_multiplier*100) -- Truncated value.
    end
   
    return tbl
end
   
function p.drop_down_table(frame)
    -- This function queries mods in concept pages, queries the subobjects in each mod page for the mod tags. Then compares the tags on the item with the mod tags. If there's a match and the spawn weighting is larger than zero, then that mod is added to a drop down list.
   
    -- Misses forsaken masters currently.
    -- Add a proper expand/collapse toggle for the entire header row so it reacts together with mw-collapsible.
    -- Show Mod group in a better way perhaps:
    -- Mod group (expanded)
    --  # to Damage (Collapsed)
    --      3 to Damage
    --      5 to Damage
    -- Add a better solution for properties inside subobjects, for extra_properties. Note that properties already queried for should not be added with this solution.
   
    -- Weapons
    -- p.drop_down_table{item = 'Rusted Hatchet', header = 'One Handed Axes'}
    -- p.drop_down_table{item = 'Stone Axe', header = 'Two Handed Axes'}


    -- Accessories
p.table_main = m_cargo.declare_factory{data=mod_map.main}
    -- p.drop_down_table{item = 'Amber Amulet', header = 'Amulets'}
p.table_mod_stats = m_cargo.declare_factory{data=mod_map.mod_stats}
   
p.table_mod_spawn_weights = m_cargo.declare_factory{data=mod_map.mod_spawn_weights}
    -- Jewels
p.table_mod_generation_weights = m_cargo.declare_factory{data=mod_map.mod_generation_weights}
    -- p.drop_down_table{item = 'Cobalt Jewel', header = 'Jewels'}
p.table_mod_sell_prices = m_cargo.declare_factory{data=mod_map.mod_sell_prices}
   
    -- Armour
    -- p.drop_down_table{item = 'Plate Vest', header = 'Armour body armours'}
    -- p.drop_down_table{item = 'Iron Greaves', header = 'Armour boots'}
    -- p.drop_down_table{item = 'Iron Gauntlets', header = 'Armour gloves'}
    -- p.drop_down_table{item = 'Iron Hat', header = 'Armour helmets'}
    -- p.drop_down_table{item = 'Splintered Tower Shield', header = 'Armour shields'}
   
    -- p.drop_down_table{item = 'Fishing Rod', header = 'FISH PLEASE', item_tags = 'fishing_rod', extra_properties = 'Has spawn weight, Has spawn chance'}


 
--
    -- Args
-- Template:Mod
    local g_args = getArgs(frame, {
--  
        parentFirst = true
p.mod = m_util.misc.invoker_factory(_mod, {
    })
     wrappers = 'Template:Mod',
    local g_frame = m_util.misc.get_frame(frame)
})
   
    local get_item_tags = p.get_item_tags{g_args.item}[1]
    local item_tags = {}
    if g_args.item_tags ~= nil then
        item_tags = m_util.string.split(g_args.item_tags, ', ')   
    else
        item_tags = m_util.string.split(get_item_tags['Has tags'], ', ')
    end
   
    local header = g_args['header']
    if header == nil then
        header = table.concat(item_tags, ', ')
    end
   
    -- Conditions
    local conditions = {}
    conditions[1] = 'concept' -- Reserve for Concepts.
    conditions[#conditions+1] = string.format('[[Has subobject::<q> [[Has tag::%s]] </q>]]', table.concat(item_tags, ' || '))
    -- Fields
    local all_fields = {
        1,
        'Is mod',
        'Has name',
        'Has level requirement',
        'Has mod group',
        'Has mod type',
        'Has stat text',
    }
   
    local extra_properties = {}
    if g_args.extra_properties ~= nil then
        extra_properties = m_util.string.split(g_args.extra_properties, ', ')
        local a = #all_fields
        for _,v in ipairs(extra_properties) do
            table.insert(all_fields, a+1, v)
        end
    end
   
    local fields = {}
    for _,v in ipairs(all_fields) do
        fields[#fields+1] = string.format('?%s', v)
    end
   
    -- Parameters
    local parameters = {
        sort = 'Has mod group, Has mod type, Has level requirement',
        -- limit = 1000, -- lets see
        offset = 0,
    }
   
    -- Create drop down lists in these sections and look in these concept pages for the modifiers:
    local section = {}
    section = {
        [1] = {
            header = 'Prefix',
            generation_type = 'prefix',
            condition = string.format('[[Concept:Spawnable named prefix %s mods]]', get_item_tags['Has mod domain text']),
        },
        [2] = {
            header = 'Suffix',
            generation_type = 'suffix',
            condition = string.format('[[Concept:Spawnable named suffix %s mods]]', get_item_tags['Has mod domain text']),
        },
        [3] = {
            header = 'Corrupted',
            generation_type = 'corrupted',
            condition = string.format('[[Concept:Spawnable corrupted %s mods]]', get_item_tags['Has mod domain text']),
            chance_multiplier = 1/4, -- See Vaal orb, for the 4 possible events.
        },
        -- [4] = {
            -- header = 'Forsaken masters',
            -- generation_type = 'master',   
            -- condition = '[[Concept:AAAAAAAAAAAAAAAAAAAA]]',
        -- },
    }
   
 
    local out = {}
   
    -- Introductory text:
    out[#out+1] = string.format('==%s== \n', header)
    out[#out+1] = '<div style="float: right; text-align:center"><div class="mw-collapsible-collapse-all" style="cursor:pointer;">[Collapse All]</div><hr><div class="mw-collapsible-expand-all" style="cursor:pointer;">[Expand All]</div></div>'   
    out[#out+1] = string.format('The table below displays the available [[modifiers]] for [[item]]s such as %s.<br><br><br>', f_item_link{get_item_tags[1]})
   
    local results = {}
    local item_mods = {}
    local tableIndex = -1       
   
    -- Loop through all the sections and query the mods in the concept pages:
    for i_type,_ in ipairs(section) do
        local generation_type = section[i_type]['generation_type']
       
        local query = {}
       
        conditions[1] = section[i_type]['condition']
        query[#query+1] = table.concat(conditions, ' ')
       
        for _, v in ipairs(fields) do
            query[#query+1] = v
        end
        for k, v in pairs(parameters) do
            query[k] = v
        end
       
        if results[generation_type] == nil then
            results[generation_type] = {}
        end
       
        -- Query the mods in the concept page, if there are too many results then repeat and add the remaining values:
        repeat
            local result = m_util.smw.query(query, g_frame)
            query.offset = query.offset + #result -- Possible error source if only one mod is missing.
           
            for _,v in ipairs(result) do
                results[generation_type][#results[generation_type]+1] = v
            end
        until #result < 1000
       
        item_mods[generation_type] = {}
       
        local container = mw.html.create('div')
            :attr('style', 'vertical-align:top; display:inline-block;')
       
        if #results[generation_type] > 0 then
            query_mod_tags = {
                string.format('[[-Has subobject::<q>%s</q>]]', table.concat(conditions, ' ')),
                '?Has tag#',
                '?Has spawn weight#',
                '?Is tag number#',
                offset = 0,
            }
           
           
            local mod_data_subobject = {}
           
            -- Query the subobjects in each mod page for the mod tags, if there are too many results then repeat and add the remaining values:
            repeat
                local results_mod_tags = m_util.smw.query(query_mod_tags, g_frame)
                query_mod_tags.offset = query_mod_tags.offset + #results_mod_tags
               
                for _,v in ipairs(results_mod_tags) do
                    local pagename = m_util.string.split(v[1], '#')[1]
                    if mod_data_subobject[pagename] == nil then
                        mod_data_subobject[pagename] = {}
                    end
                   
                    local subobjectname = v[1]
                    mod_data_subobject[pagename][subobjectname] = v
                end               
            until #results_mod_tags < 1000
           
            -- Loop through all the modifiers from the concept pages:
            local last
            for i, v in ipairs(results[generation_type]) do
                local pagename = results[generation_type][i][1]
               
               
                -- Loop through all the modifier tags until they match the item tags:
                local j = 0
                local tag_match_stop
                repeat
                    j = j+1
                    local subobjectname = string.format('%s#%s_%s', pagename, 'spawn_weight', j)
                   
                    -- We know that the mod query found subobjects with tags. If the subobject missed any subobject for some reason make sure to catch that potential error and requery only for that particular mod page:
                    local j2 = 0
                    while not pcall(function () return mod_data_subobject[pagename][subobjectname]['Has tag'] end) do
                        j2 = j2+1
                        local query_mod_tags_missed = query_mod_tags
                        query_mod_tags_missed[1] = string.format('[[-Has subobject::<q>%s [[%s]]</q>]]', table.concat(conditions, ' '), pagename)
                        local results_mod_tags =  m_util.smw.query(query_mod_tags, g_frame)
                       
                        for _,v in ipairs(results_mod_tags) do
                            if mod_data_subobject[pagename] == nil then
                                mod_data_subobject[pagename] = {}
                            end
                            mod_data_subobject[pagename][v[1]] = v
                        end
                   
                       
                        if (not pcall(function () return mod_data_subobject[pagename][subobjectname]['Has tag'] end)) and j2 == 3 then
                            error(string.format('mod_data_subobject doesnt have tags: pagename = %s, subobjectname = %s ? Queried %s times. Try to null edit the mod page and the mod list page.', pagename, subobjectname, j2))
                        end
                    end
                   
                    local mod_tag = mod_data_subobject[pagename][subobjectname]['Has tag']
                    local mod_tag_weight = tonumber(mod_data_subobject[pagename][subobjectname]['Has spawn weight'])
                   
                    -- Loop through the item tags until it matches the mod tag and the mod tag has a value larger than zero:
                    local y = 0
                    local tag_match_add
                    repeat      
                        y = y+1
                        tag_match_stop = ((mod_tag == item_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (mod_data_subobject[pagename][subobjectname] == nil)
                        tag_match_add = (mod_tag == item_tags[y]) and ((mod_tag_weight or -1) > 0)
                    until tag_match_stop or y == #item_tags
                   
                    -- If there's a match then save that mod and other interesting information:
                    if tag_match_add then
                       
                        -- Assume that the mod is global then go through all the subobjects and check if any of the stats are local:
                        local mod_scope = 'Global'
                        for subobject, subobject_val in pairs(mod_data_subobject[pagename]) do
                       
                            -- If at least one stat is local then change the scope of the mod to local:
                            if subobject:find('stat.*local') ~= nil then
                                mod_scope = 'Local'
                            end
                           
                            -- Complement with the properties the matching subobject has:
                            if subobject == subobjectname then
                                for property,w in pairs(subobject_val) do
                                    if results[generation_type][i][property] == nil then
                                        results[generation_type][i][property] = {}
                                    end
                                   
                                    results[generation_type][i][property] = w
                                end
                            end
                        end
                       
                        -- Save the matching modifier and all other interesting properties:
                        local a = #item_mods[generation_type]
                        item_mods[generation_type][a+1] = {}
                        for _,property in ipairs(all_fields) do
                            item_mods[generation_type][a+1][property] = results[generation_type][i][property]
                        end
                        item_mods[generation_type][a+1]['Has mod scope'] = mod_scope
                    end
                until tag_match_stop
            end
           
            -- If the user wants to see the spawn chance then do the calculations and save that result as well:
            for _,v in ipairs(extra_properties) do
                if v == 'Has spawn chance' then
                    item_mods[generation_type] = p.get_spawn_chance{tbl = item_mods[generation_type], chance_multiplier = section[i_type]['chance_multiplier']}
                    break
                end
            end
           
            -- Create the drop down table with <table></table>:
            local headers = container
            headers
                :tag('h3')
                    :wikitext(string.format('%s', section[i_type]['header']))
                    :done()
                :done()
               
            -- Loop through and add all matching mods to the <table>.
            local tbl, last
            for _, mod_properties in ipairs(item_mods[generation_type]) do
           
                -- If the last mod group is different to the current mod group then assume the mod isn't related and start a new drop down list:
                if mod_properties['Has mod group'] ~= last then
                   
                    -- Check through all the mods and see if there are multiple mod types within the same mod group:
                    local count = {}
                    for _, n in ipairs(item_mods[generation_type]) do
                       
                        -- If the mod has the same mod group, then add the mod type to the counter. Only unique mod types matter so the number is just a dummy value:
                        if n['Has mod group'] == mod_properties['Has mod group'] then
                            count[n['Has mod type']] = 1
                        end
                    end
                   
                    -- Calculate how many unique mod types with the same mod group there are:
                    local number_of_mod_types = 0
                    for _ in pairs(count) do
                        number_of_mod_types = number_of_mod_types + 1
                    end
                   
                    -- If there are multiple unique mod types with the same mod group then change the style of the drop down list to indicate it:
                    if number_of_mod_types > 1 then
                        tbl_caption = string.format('%s', m_util.html.poe_color('mod', 'Mod group: ' .. mod_properties['Has mod group']))
                    else
                        tbl_caption = string.format('%s (%s)', m_util.html.poe_color('mod', p.header(mod_properties['Has stat text'])), mod_properties['Has mod scope'])
                    end
                   
                    -- Add class and style to the <table>:
                    tableIndex = tableIndex+1
                    tbl = container:tag('table')
                    tbl
                        :attr('class', 'mw-collapsible mw-collapsed')
                        :attr('style', 'text-align:left; line-height:1.60em; width:810px;')
                        :tag('th')
                            :attr('class', string.format('mw-customtoggle-%s', tableIndex))
                            :attr('style', 'text-align:left; line-height:1.40em; border-bottom:1pt solid dimgrey;')
                            :attr('colspan', '3' .. #extra_properties)
                            :wikitext(tbl_caption)
                            :done()
                        :done()   
                end
               
                -- If the mod has no name then use the mod id:
                local mod_name = mod_properties['Has name']
                if  mod_name == '' then
                    mod_name = mod_properties['Is mod']
                end
               
                -- Check if there are any extra properties to show in the drop down list and then add a cell for that, add this node at the end of the table row:
                local td = mw.html.create('td')
                if extra_properties ~= nil then
                    for _, extra_property in ipairs(extra_properties) do
                        td
                            :attr('width', '*')
                            :wikitext(string.format('%s:&nbsp;%s ', extra_property, mod_properties[extra_property]))
                            :done()
                    end
                end
               
                -- Add a table row with the interesting properties that modifier has:
                tbl
                    :tag('tr')
                        :attr('class', 'mw-collapsible mw-collapsed')
                        :attr('id', string.format('mw-customcollapsible-%s', tableIndex))
                        :tag('td')
                            :attr('width', '160')
                            :wikitext(string.format('&nbsp;&nbsp;&nbsp;[[%s|%s]]', mod_properties[1], mod_name:gsub('%s', '&nbsp;')))
                            :done()
                        :tag('td')
                            :attr('width', '1')
                            :wikitext(string.format('%s&nbsp;%s',game.level_requirement['short_upper']:gsub('%s', '&nbsp;'), mod_properties['Has level requirement']))
                            :done()       
                        :tag('td')
                            :attr('width', '*')
                            :wikitext(string.format('%s', m_util.html.poe_color('mod', mod_properties['Has stat text']:gsub('<br>', ', '))  ))
                            :done()
                        :node(td)
                        :done()
                    :done()
               
                -- Save the last mod group for later comparison:
                last = mod_properties['Has mod group']
            end   
        end
       
        out[#out+1] = tostring(container)
    end
   
    return table.concat(out,'')
end


return p
return p

Latest revision as of 20:25, 25 September 2024

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


Lua logo

This module depends on the following other modules:

Module for handling for modifiers with Cargo support.

List of currently implemented templates

-------------------------------------------------------------------------------
-- 
--                            Module:Mod
-- 
-- This module implements Template:Mod
-------------------------------------------------------------------------------

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

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

local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or mw.loadData('Module:Game')

-- Lazy loading
local f_item_table -- require('Module:Item table').item_table

-- 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:Mod/config/sandbox') or mw.loadData('Module:Mod/config')

local i18n = cfg.i18n

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

local h = {}

-- Lazy loading for Module:Item table
function h.item_table(args)
    if not f_item_table then
        f_item_table = require('Module:Item table').item_table
    end
    return f_item_table(args)
end

function h.set_weights(tpl_args, args)
    -- Parses a weighted pair of lists and sets properties
    --
    -- tpl_args: argument table to work with
    -- args:
    --   prefix - input prefix for parsing the arguments from tpl_args
    --   table_map - cargo table map

    args = args or {}
    for i=1, math.huge do -- repeat until no more weights are found
        local prefix = args.prefix .. i
        local params = {
            tag = string.format('%s_tag', prefix),
            value = string.format('%s_value', prefix),
        }
        local tag = tpl_args[params.tag]
        local value = tpl_args[params.value]
        if tag == nil and value == nil then
            break
        end
        if tag == nil or value == nil then
            error(string.format(i18n.errors.invalid_weight, params.tag, params.value))
        end
        value = m_util.cast.number(value, {min = 0})

        -- Store to cargo table unless tag = default and value = 0
        if tag ~= 'default' or value ~= 0 then
            m_cargo.store({
                _table = args.table_map.table,
                [args.table_map.fields.ordinal.field] = i,
                [args.table_map.fields.tag.field] = tag,
                [args.table_map.fields.value.field] = value,
            })
        end
    end
end

-- ----------------------------------------------------------------------------
-- Templates
-- ----------------------------------------------------------------------------

--
-- Template: Mod
--

local mod_map = {
    main = {
        table = 'mods',
        display_order = {'id', 'name', 'mod_groups', 'mod_type', 'domain', 'generation_type', 'required_level', 'stat_text', 'granted_buff_id', 'granted_buff_value', 'granted_skill', 'tags', 'tier_text'},
        order = {'id', 'name', 'mod_groups', 'mod_type', 'domain', 'generation_type', 'required_level', 'stat_text', 'stat_text_raw', 'granted_buff_id', 'granted_buff_value', 'granted_skill', 'tags', 'tier_text'},
        fields = {
            id = {
                name = 'id',
                field = 'id',
                type = 'String',
                wikitext = i18n.data_sheet.id,
                func = function (tpl_args, value)
                    -- Validate that the id is unique
                    local results = m_cargo.query(
                        {'mods'},
                        {'mods._pageName'},
                        {
                            where = string.format(
                                'mods.id = "%s" AND mods._pageName != "%s"',
                                value,
                                m_cargo.addslashes(mw.title.getCurrentTitle().prefixedText)
                            )
                        }
                    )
                    if #results > 0 then
                        error(string.format(i18n.errors.duplicate_mod_id, results[1]['mods._pageName']))
                    end
                    return value
                end
            },
            name = {
                name = 'name',
                field = 'name',
                type = 'String',
                wikitext = i18n.data_sheet.name,
            },
            mod_groups = {
                name = 'mod_groups',
                field = 'mod_groups',
                type = 'List (,) of String',
                wikitext = i18n.data_sheet.mod_groups,
                display = function (value)
                    return table.concat(value, ', ')  
                end,
                default = {},
            },
            mod_type = {
                name = 'mod_type',
                field = 'mod_type',
                type = 'String',
                wikitext = i18n.data_sheet.mod_type,
            },
            domain = {
                name = 'domain',
                field = 'domain',
                type = 'Integer',
                wikitext = 'Mod domain',
                display = function (value)
                    return string.format(i18n.data_sheet.domain_fmt, m_game.constants.mod.domains[value]['short_upper'], value)
                end,
            },
            generation_type = {
                name = 'generation_type',
                field = 'generation_type',
                type = 'Integer',
                wikitext = i18n.data_sheet.generation_type,
                display = function (value)
                    return string.format(i18n.data_sheet.generation_type_fmt, m_game.constants.mod.generation_types[value]['short_upper'], value)
                end,
            },
            required_level = {
                name = 'required_level',
                field = 'required_level',
                type = 'Integer',
                wikitext = i18n.data_sheet.required_level,
            },
            stat_text = {
                name = 'stat_text',
                field = 'stat_text',
                type = 'Text',
                wikitext = i18n.data_sheet.stat_text,
            },
            stat_text_raw = {
                name = nil,
                field = 'stat_text_raw',
                type = 'Text',
                func = function (tpl_args, value)
                    if tpl_args.stat_text then
                        -- Strip wikilinks and html, but keep any line break tags
                        value = m_util.string.strip_wikilinks(tpl_args.stat_text)
                        value = mw.ustring.gsub(value, '<br */?>', '�')
                        value = m_util.string.strip_html(value)
                        value = mw.ustring.gsub(value, '�', '<br>')
                    end
                    return value
                end
            },
            granted_buff_id = {
                name = 'granted_buff_id',
                field = 'granted_buff_id',
                type = 'String',
                wikitext = i18n.data_sheet.granted_buff_id,
            },
            granted_buff_value = {
                name = 'granted_buff_value',
                field = 'granted_buff_value',
                type = 'Integer',
                wikitext = i18n.data_sheet.granted_buff_value,
            },
            granted_skill = {
                name = 'granted_skill',
                field = 'granted_skill',
                type = 'String',
                wikitext = i18n.data_sheet.granted_skill,
            },
            tags = {
                name = 'tags',
                field = 'tags',
                type = 'List (,) of String',
                wikitext = 'Tags', 
                display = function (value)
                    return table.concat(value, ', ')
                end,
                default = {},
            },
            tier_text = {
                name = 'tier_text',
                field = 'tier_text',
                type = 'Text',
                wikitext = i18n.data_sheet.tier_text,
            },
        },
    },
    mod_stats = {
        table = 'mod_stats',
        fields = {
            id = {
                field = 'id',
                type = 'String',
            },
            min = {
                field = 'min',
                type = 'Integer',
            },
            max = {
                field = 'max',
                type = 'Integer',
            },
        },
    },
    mod_spawn_weights = {
        table = 'mod_spawn_weights',
        fields = {
            ordinal = {
                field = 'ordinal',
                type = 'Integer',
            },
            tag = {
                field = 'tag',
                type = 'String',
            },
            value = {
                field = 'value',
                type = 'Integer',
            },
        },
    },
    mod_generation_weights = {
        table = 'mod_generation_weights',
        fields = {
            ordinal = {
                field = 'ordinal',
                type = 'Integer',
            },
            tag = {
                field = 'tag',
                type = 'String',
            },
            value = {
                field = 'value',
                type = 'Integer',
            },
        },
    },
    mod_sell_prices = {
        table = 'mod_sell_prices',
        order = {'name', 'amount'},
        fields = {
            name = {
                name = 'name',
                field = 'name',
                type = 'String',
                func = function (value) return value end,
            },
            amount = {
                name = 'amount',
                field = 'amount',
                type = 'Integer',
                func = tonumber,
            },
        },
    },
}

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

local function _mod(tpl_args)
    -- p.mod{id = "LocalIncreasedPhysicalDamagePercentUniqueOneHandSword2", name = "", mod_groups = "LocalPhysicalDamagePercent, Dexterity", domain = "1", generation_type = "3", required_level = "1", mod_type = "LocalPhysicalDamagePercent", stat_text = "150% increased Physical Damage", stat1_id = "local_physical_damage_+%", stat1_min = "150", stat1_max = "150"}
    
    --
    -- Validate and store
    --

    -- Validate single value properties and set them
    m_cargo.store_mapped_args{
        tpl_args = tpl_args,
        table_map = mod_map.main,
    }
    
    -- Validate and store stats
    m_util.args.stats(tpl_args)
    for _, stat_data in pairs(tpl_args.stats) do
        m_cargo.store({
            _table = 'mod_stats', 
            id = stat_data.id,
            min = stat_data.min,
            max = stat_data.max,
        })
    end
    
    -- Validate and store spawn weights
    h.set_weights(tpl_args, {
        prefix = 'spawn_weight',
        table_map = mod_map.mod_spawn_weights
    })
    
    -- Validate and store generation weights
    h.set_weights(tpl_args, {
        prefix = 'generation_weight',
        table_map = mod_map.mod_generation_weights
    })
    
    -- Validate and store mod sell values
    local i = 0
    local names = {}
    local sell_prices = {}
    repeat 
        i = i + 1
        
        local id = {}
        local value = {}
        for key, data in pairs(mod_map.mod_sell_prices.fields) do
            id[key] = string.format('%s%s_%s', 'sell_price', i, data.name)
            value[key] = data.func(tpl_args[id[key]])
        end
        
        if value.name == nil and value.amount == nil then
            value = nil
        elseif value.name ~= nil and value.amount ~= nil then
            if names[value.name] then
                error(i18n.errors.sell_price_duplicate_name)
            else
                names[value.name] = true
            end

            local cargo_data = {
                _table = mod_map.mod_sell_prices.table,
            }
            for key, data in pairs(mod_map.mod_sell_prices.fields) do
                cargo_data[data.field] = value[key]
            end
            m_cargo.store(cargo_data)
            
            sell_prices[#sell_prices+1] = value
        else
            error (string.format(i18n.errors.sell_price_missing_arguments, id.name, id.amount))
        end
        
    until value == nil

    -- Attach to tables
    mw.getCurrentFrame():expandTemplate{title = 'Template:Mod/cargo/mods/attach'}
    mw.getCurrentFrame():expandTemplate{title = 'Template:Mod/cargo/mod stats/attach'}
    mw.getCurrentFrame():expandTemplate{title = 'Template:Mod/cargo/mod spawn weights/attach'}
    mw.getCurrentFrame():expandTemplate{title = 'Template:Mod/cargo/mod generation weights/attach'}
    mw.getCurrentFrame():expandTemplate{title = 'Template:Mod/cargo/mod sell prices/attach'}
    
    --
    -- Display
    --
    
    local container = mw.html.create('div')
        :addClass('modbox')
    
    -- core stats
    
    local tbl = container:tag('table')
        :addClass('wikitable')
    
    for _, key in ipairs(mod_map.main.display_order) do
        local data = mod_map.main.fields[key]
        local text = tpl_args[key]
        if type(data.display) == 'function' then
            text = data.display(text)
        end
        tbl
            :tag('tr')
                :tag('th')
                    :wikitext(data.wikitext)
                    :done()
                :tag('td')
                    :wikitext(text)
                    :done()
                :done()
            :done()
    end
    
    -- stat table
    
    tbl = container:tag('table')
    tbl
        :addClass('wikitable sortable')
        :tag('tr')
            :tag('th')
                :attr('colspan', 4)
                :wikitext(i18n.data_sheet.stats)
                :done()
            :done()
        :tag('tr')
            :tag('th')
                :wikitext(i18n.data_sheet.ordinal)
                :done()
            :tag('th')
                :wikitext(i18n.data_sheet.stat_id)
                :done()
            :tag('th')
                :wikitext(i18n.data_sheet.min)
                :done()
            :tag('th')
                :wikitext(i18n.data_sheet.max)
                :done()
            :done()
        :done()
        
    for i=1, #tpl_args.stats do
        local value = {
            id = tpl_args['stat' .. i .. '_id'],
            min = tpl_args['stat' .. i .. '_min'],
            max = tpl_args['stat' .. i .. '_max'],
        }
        
        if value.id then
            tbl
                :tag('tr')
                    :tag('td')
                        :wikitext(i)
                        :done()
                    :tag('td')
                        :wikitext(value.id)
                        :done()
                    :tag('td')
                        :wikitext(value.min)
                        :done()
                    :tag('td')
                        :wikitext(value.max)
                        :done()
                    :done()
                :done()
        end
    end
    
    -- spawn weight table
    
    tbl = container:tag('table')
    tbl
        :addClass('wikitable sortable')
        :tag('tr')
            :tag('th')
                :attr('colspan', 3)
                :wikitext(i18n.data_sheet.spawn_weights)
                :done()
            :done()
        :tag('tr')
            :tag('th')
                :wikitext(i18n.data_sheet.ordinal)
                :done()
            :tag('th')
                :wikitext(i18n.data_sheet.tag)
                :done()
            :tag('th')
                :wikitext(i18n.data_sheet.weight)
                :done()
            :done()
        :done()
        
    i = 0
    local value = nil
    repeat
        i = i + 1
        value = {
            tag = tpl_args[string.format('spawn_weight%s_tag', i)],
            value = tpl_args[string.format('spawn_weight%s_value', i)],
        }
        
        if value.tag then
            tbl
                :tag('tr')
                    :tag('td')
                        :wikitext(i)
                        :done()
                    :tag('td')
                        :wikitext(value.tag)
                        :done()
                    :tag('td')
                        :wikitext(value.value)
                        :done()
                    :done()
                :done()
        end
    until value.tag == nil
    
    -- generation weight table
    
    tbl = container:tag('table')
    tbl
        :addClass('wikitable sortable')
        :tag('tr')
            :tag('th')
                :attr('colspan', 3)
                :wikitext(i18n.data_sheet.generation_weights)
                :done()
            :done()
        :tag('tr')
            :tag('th')
                :wikitext(i18n.data_sheet.ordinal)
                :done()
            :tag('th')
                :wikitext(i18n.data_sheet.tag)
                :done()
            :tag('th')
                :wikitext(i18n.data_sheet.weight)
                :done()
            :done()
        :done()
    
    i = 0
    value = nil
    repeat
        i = i + 1
        value = {
            tag = tpl_args[string.format('generation_weight%s_tag', i)],
            value = tpl_args[string.format('generation_weight%s_value', i)],
        }
        
        if value.tag then
            tbl
                :tag('tr')
                    :tag('td')
                        :wikitext(i)
                        :done()
                    :tag('td')
                        :wikitext(value.tag)
                        :done()
                    :tag('td')
                        :wikitext(value.value)
                        :done()
                    :done()
                :done()
        end
    until value.tag == nil
    
    -- Sell prices
    tbl = container:tag('table')
    tbl
        :addClass('wikitable sortable')
        :tag('tr')
            :tag('th')
                :attr('colspan', 2)
                :wikitext(i18n.data_sheet.sell_price)
                :done()
            :done()
        :tag('tr')
            :tag('th')
                :wikitext(i18n.data_sheet.ordinal)
                :done()
            :tag('th')
                :wikitext(i18n.data_sheet.item)
                :done()
            :done()
        :done()
    
    for i, value in ipairs(sell_prices) do
        tbl
            :tag('tr')
                :tag('td')
                    :wikitext(value.amount)
                    :done()
                :tag('td')
                    :wikitext(string.format('[[%s]]', value.name))
                    :done()
                :done()
    end
    
    -- Generic messages on the page
    
    local out = {}
    
    if mw.ustring.find(tpl_args['id'], '_') then
        out[#out+1] = mw.getCurrentFrame():expandTemplate{ title = 'Incorrect title', args = { title=tpl_args['id'] } } .. '\n\n\n'
    end
    
    if tpl_args['name'] then
        out[#out+1] = string.format(i18n.sections.intro_named_id, tpl_args['id'], tpl_args['name'])
    else
        out[#out+1] = string.format(i18n.sections.intro_unnamed_id, tpl_args['id'])
    end

    -- Item usage
    local items = m_cargo.query(
        {'item_mods'},
        {'item_mods._pageName=page'},
        {
            where = string.format(
                'item_mods.id = "%s"',
                tpl_args['id']
            )
        }
    )
    if #items > 0 then
        local html = mw.html.create()
            :tag('h2')
                :wikitext(i18n.sections.items)
                :done()
            :tag('p')
                :wikitext(i18n.sections.used_by_items)
                :done()
        out[#out+1] = tostring(html)
        out[#out+1] = h.item_table{
            q_tables = 'items',
            q_where = string.format(
                'items._pageName IN ("%s")',
                table.concat(m_util.table.column(items, 'page'), '","')
            ),
            q_orderBy = 'items.name ASC',
        }
    end
    
    -- Categories
    
    local cats = {i18n.categories.mods}
    
    -- Done -> output
    
    return tostring(container) .. m_util.misc.add_category(cats) .. '\n' .. table.concat(out)
end

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

local p = {}

p.table_main = m_cargo.declare_factory{data=mod_map.main}
p.table_mod_stats = m_cargo.declare_factory{data=mod_map.mod_stats}
p.table_mod_spawn_weights = m_cargo.declare_factory{data=mod_map.mod_spawn_weights}
p.table_mod_generation_weights = m_cargo.declare_factory{data=mod_map.mod_generation_weights}
p.table_mod_sell_prices = m_cargo.declare_factory{data=mod_map.mod_sell_prices}

--
-- Template:Mod
-- 
p.mod = m_util.misc.invoker_factory(_mod, {
    wrappers = 'Template:Mod',
})

return p