Module:Mod: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
(Validate that the id is unique)
No edit summary
 
(2 intermediate revisions by the same user not shown)
Line 14: Line 14:


local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or mw.loadData('Module:Game')
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
-- The cfg table contains all localisable strings and configuration, to make it
Line 26: Line 29:


local h = {}
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)
function h.set_weights(tpl_args, args)
Line 82: Line 93:
                 field = 'id',
                 field = 'id',
                 type = 'String',
                 type = 'String',
                 wikitext = i18n.tooltips.id,
                 wikitext = i18n.data_sheet.id,
                 func = function (tpl_args, value)
                 func = function (tpl_args, value)
                     -- Validate that the id is unique
                     -- Validate that the id is unique
Line 106: Line 117:
                 field = 'name',
                 field = 'name',
                 type = 'String',
                 type = 'String',
                 wikitext = i18n.tooltips.name,
                 wikitext = i18n.data_sheet.name,
             },
             },
             mod_groups = {
             mod_groups = {
Line 112: Line 123:
                 field = 'mod_groups',
                 field = 'mod_groups',
                 type = 'List (,) of String',
                 type = 'List (,) of String',
                 wikitext = i18n.tooltips.mod_groups,
                 wikitext = i18n.data_sheet.mod_groups,
                 display = function (value)
                 display = function (value)
                     return table.concat(value, ', ')   
                     return table.concat(value, ', ')   
Line 122: Line 133:
                 field = 'mod_type',
                 field = 'mod_type',
                 type = 'String',
                 type = 'String',
                 wikitext = i18n.tooltips.mod_type,
                 wikitext = i18n.data_sheet.mod_type,
             },
             },
             domain = {
             domain = {
Line 130: Line 141:
                 wikitext = 'Mod domain',
                 wikitext = 'Mod domain',
                 display = function (value)
                 display = function (value)
                     return string.format(i18n.tooltips.domain_fmt, m_game.constants.mod.domains[value]['short_upper'], value)
                     return string.format(i18n.data_sheet.domain_fmt, m_game.constants.mod.domains[value]['short_upper'], value)
                 end,
                 end,
             },
             },
Line 137: Line 148:
                 field = 'generation_type',
                 field = 'generation_type',
                 type = 'Integer',
                 type = 'Integer',
                 wikitext = i18n.tooltips.generation_type,
                 wikitext = i18n.data_sheet.generation_type,
                 display = function (value)
                 display = function (value)
                     return string.format(i18n.tooltips.generation_type_fmt, m_game.constants.mod.generation_types[value]['short_upper'], value)
                     return string.format(i18n.data_sheet.generation_type_fmt, m_game.constants.mod.generation_types[value]['short_upper'], value)
                 end,
                 end,
             },
             },
Line 146: Line 157:
                 field = 'required_level',
                 field = 'required_level',
                 type = 'Integer',
                 type = 'Integer',
                 wikitext = i18n.tooltips.required_level,
                 wikitext = i18n.data_sheet.required_level,
             },
             },
             stat_text = {
             stat_text = {
Line 152: Line 163:
                 field = 'stat_text',
                 field = 'stat_text',
                 type = 'Text',
                 type = 'Text',
                 wikitext = i18n.tooltips.stat_text,
                 wikitext = i18n.data_sheet.stat_text,
             },
             },
             stat_text_raw = {
             stat_text_raw = {
Line 160: Line 171:
                 func = function (tpl_args, value)
                 func = function (tpl_args, value)
                     if tpl_args.stat_text then
                     if tpl_args.stat_text then
                         value = string.gsub(
                        -- Strip wikilinks and html, but keep any line break tags
                            -- [[x]] -> x
                         value = m_util.string.strip_wikilinks(tpl_args.stat_text)
                            string.gsub(
                        value = mw.ustring.gsub(value, '<br */?>', '')
                                tpl_args.stat_text,
                        value = m_util.string.strip_html(value)
                                '%[%[([^%]|]+)%]%]', '%1'
                        value = mw.ustring.gsub(value, '', '<br>')
                            ),  
                            -- [[x|y]] -> y
                            '%[%[[^|]+|([^%]|]+)%]%]', '%1'
                        )
                     end
                     end
                     return value
                     return value
Line 177: Line 184:
                 field = 'granted_buff_id',
                 field = 'granted_buff_id',
                 type = 'String',
                 type = 'String',
                 wikitext = i18n.tooltips.granted_buff_id,
                 wikitext = i18n.data_sheet.granted_buff_id,
             },
             },
             granted_buff_value = {
             granted_buff_value = {
Line 183: Line 190:
                 field = 'granted_buff_value',
                 field = 'granted_buff_value',
                 type = 'Integer',
                 type = 'Integer',
                 wikitext = i18n.tooltips.granted_buff_value,
                 wikitext = i18n.data_sheet.granted_buff_value,
             },
             },
             granted_skill = {
             granted_skill = {
Line 189: Line 196:
                 field = 'granted_skill',
                 field = 'granted_skill',
                 type = 'String',
                 type = 'String',
                 wikitext = i18n.tooltips.granted_skill,
                 wikitext = i18n.data_sheet.granted_skill,
             },
             },
             tags = {
             tags = {
Line 205: Line 212:
                 field = 'tier_text',
                 field = 'tier_text',
                 type = 'Text',
                 type = 'Text',
                 wikitext = i18n.tooltips.tier_text,
                 wikitext = i18n.data_sheet.tier_text,
             },
             },
         },
         },
Line 403: Line 410:
             :tag('th')
             :tag('th')
                 :attr('colspan', 4)
                 :attr('colspan', 4)
                 :wikitext(i18n.tooltips.stats)
                 :wikitext(i18n.data_sheet.stats)
                 :done()
                 :done()
             :done()
             :done()
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.ordinal)
                 :wikitext(i18n.data_sheet.ordinal)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.stat_id)
                 :wikitext(i18n.data_sheet.stat_id)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.min)
                 :wikitext(i18n.data_sheet.min)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.max)
                 :wikitext(i18n.data_sheet.max)
                 :done()
                 :done()
             :done()
             :done()
Line 457: Line 464:
             :tag('th')
             :tag('th')
                 :attr('colspan', 3)
                 :attr('colspan', 3)
                 :wikitext(i18n.tooltips.spawn_weights)
                 :wikitext(i18n.data_sheet.spawn_weights)
                 :done()
                 :done()
             :done()
             :done()
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.ordinal)
                 :wikitext(i18n.data_sheet.ordinal)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.tag)
                 :wikitext(i18n.data_sheet.tag)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.weight)
                 :wikitext(i18n.data_sheet.weight)
                 :done()
                 :done()
             :done()
             :done()
Line 507: Line 514:
             :tag('th')
             :tag('th')
                 :attr('colspan', 3)
                 :attr('colspan', 3)
                 :wikitext(i18n.tooltips.generation_weights)
                 :wikitext(i18n.data_sheet.generation_weights)
                 :done()
                 :done()
             :done()
             :done()
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.ordinal)
                 :wikitext(i18n.data_sheet.ordinal)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.tag)
                 :wikitext(i18n.data_sheet.tag)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.weight)
                 :wikitext(i18n.data_sheet.weight)
                 :done()
                 :done()
             :done()
             :done()
Line 556: Line 563:
             :tag('th')
             :tag('th')
                 :attr('colspan', 2)
                 :attr('colspan', 2)
                 :wikitext(i18n.tooltips.sell_price)
                 :wikitext(i18n.data_sheet.sell_price)
                 :done()
                 :done()
             :done()
             :done()
         :tag('tr')
         :tag('tr')
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.ordinal)
                 :wikitext(i18n.data_sheet.ordinal)
                 :done()
                 :done()
             :tag('th')
             :tag('th')
                 :wikitext(i18n.tooltips.item)
                 :wikitext(i18n.data_sheet.item)
                 :done()
                 :done()
             :done()
             :done()
Line 590: Line 597:
      
      
     if tpl_args['name'] then
     if tpl_args['name'] then
   
         out[#out+1] = string.format(i18n.sections.intro_named_id, tpl_args['id'], tpl_args['name'])
         out[#out+1] = string.format(i18n.tooltips.intro_named_id, tpl_args['id'], tpl_args['name'])
     else
     else
         out[#out+1] = string.format(i18n.tooltips.intro_unnamed_id, tpl_args['id'])
         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
     end
      
      

Latest revision as of 20:20, 21 April 2024

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


This module is used on 34,000+ pages.

To avoid major disruption and server load, do not make unnecessary edits to this module. Test changes to this module first using its /sandbox and /testcases subpages . All of the changes can then be applied to this module in a single edit.

Consider discussing changes on the talk page or on Discord before implementing them.

Lua logo

This module depends on the following other modules:

Module for handling for modifiers with Cargo support.

List of currently implemented templates

de:Modul:Mod

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