Module:Mod: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
(the datamine data in the past actually refer to the aura radius)
No edit summary
 
(5 intermediate revisions by 2 users not shown)
Line 6: Line 6:
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------


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


local m_game = mw.loadData('Module:Game')
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Mod')


local f_item_link = require('Module:Item link').item_link
local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or mw.loadData('Module:Game')


-- ----------------------------------------------------------------------------
-- Lazy loading
-- Strings
local f_item_table -- require('Module:Item table').item_table
-- ----------------------------------------------------------------------------
-- 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 = {
-- The cfg table contains all localisable strings and configuration, to make it
    categories = {
-- easier to port this module to another wiki.
        mods = 'Mods',
local cfg = use_sandbox and mw.loadData('Module:Mod/config/sandbox') or mw.loadData('Module:Mod/config')
    },
   
    tooltips = {
        -- intro texts
        intro_named_id = "'''%s''' is the internal id of [[modifier]] '''%s'''.\n",
        intro_unnamed_id = "'''%s''' is the internal id of an unnamed [[modifier]].\n",
       
        -- core data
        id = 'Mod Id',
        name = 'Name',
        mod_groups = 'Groups',
        mod_type = 'Mod type',
        domain = 'Domain',
        domain_fmt = '%s (Id: %s)',
        generation_type = 'Generation type',
        generation_type_fmt = '%s (Id: %s)',
        required_level = 'Req. Level',
        stat_text = 'Effect',
        granted_buff_id = 'Granted Buff Id',
        granted_buff_value = 'Aura Radius (if any)',
        granted_skill = 'Granted Skill',
        tags = 'Tags',
        tier_text = 'Tier Text',
       
        -- shared
        ordinal = '#',
       
        -- stats
        stats = 'Stats',
        stat_id = 'Stat Id',
        min = 'Minimum',
        max = 'Maximum',
       
        -- weights
        spawn_weights = 'Spawn weights',
        generation_weights = 'Generation weights',
        tag = 'Tag',
        weight = 'Weight',
       
        -- sell price
        sell_price = 'Modifier sell price',
        item = 'Item',
    },
   
    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',
        invalid_weight = 'Parameters %s and %s must be given as a pair',
    },


}
local i18n = cfg.i18n


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- utility / Helper functions
-- Helper functions
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------


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 140: Line 93:
                 field = 'id',
                 field = 'id',
                 type = 'String',
                 type = 'String',
                 wikitext = i18n.tooltips.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 = {
Line 146: Line 117:
                 field = 'name',
                 field = 'name',
                 type = 'String',
                 type = 'String',
                 wikitext = i18n.tooltips.name,
                 wikitext = i18n.data_sheet.name,
             },
             },
             mod_groups = {
             mod_groups = {
Line 152: 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, ', ')   
                 end,
                 end,
Line 162: 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 170: 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 177: 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 186: 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 192: 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 198: Line 169:
                 field = 'stat_text_raw',
                 field = 'stat_text_raw',
                 type = 'Text',
                 type = 'Text',
                 func = function(tpl_args, value)
                 func = function (tpl_args, value)
                     if tpl_args.stat_text then
                     if tpl_args.stat_text then
                         tpl_args.stat_text_raw = 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, '%[%[([^%]|]+)%]%]', '%1'
                        value = m_util.string.strip_html(value)
                            ),  
                        value = mw.ustring.gsub(value, '', '<br>')
                            -- [[x|y]] -> y
                            '%[%[[^|]+|([^%]|]+)%]%]', '%1'
                        )
                     end
                     end
                     return tpl_args.stat_text_raw
                     return value
                 end
                 end
             },
             },
Line 216: 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 222: 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 228: 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 235: Line 203:
                 type = 'List (,) of String',
                 type = 'List (,) of String',
                 wikitext = 'Tags',  
                 wikitext = 'Tags',  
                 display = function(value)
                 display = function (value)
                     return table.concat(value, ', ')
                     return table.concat(value, ', ')
                 end,
                 end,
                 default = {},
                 default = {},
Line 244: Line 212:
                 field = 'tier_text',
                 field = 'tier_text',
                 type = 'Text',
                 type = 'Text',
                 wikitext = i18n.tooltips.tier_text,
                 wikitext = i18n.data_sheet.tier_text,
             },
             },
         },
         },
Line 327: Line 295:
      
      
     --
     --
     -- Validation & semantic properties
     -- Validate and store
     --
     --
   
 
     -- Validate single value properties and set them
     -- Validate single value properties and set them
     -- TODO: Possibly switch to m_cargo.parse_field_arguments()
     m_cargo.store_mapped_args{
    local cargo_values = m_util.args.from_cargo_map{
         tpl_args = tpl_args,
         tpl_args=tpl_args,
         table_map = mod_map.main,
         table_map=mod_map.main,
        rtr = true,
     }
     }
    m_cargo.store(cargo_values)
      
      
     -- Validate and set the stat subobjects
     -- Validate and store stats
     m_util.args.stats(tpl_args)
     m_util.args.stats(tpl_args)
     for _, stat_data in pairs(tpl_args.stats) do
     for _, stat_data in pairs(tpl_args.stats) do
Line 351: Line 315:
     end
     end
      
      
     -- Validate and set spawn weight subobjects
     -- Validate and store spawn weights
     h.set_weights(tpl_args, {
     h.set_weights(tpl_args, {
         prefix = 'spawn_weight',
         prefix = 'spawn_weight',
Line 357: Line 321:
     })
     })
      
      
     -- Validate and set generation weight subobjects
     -- Validate and store generation weights
     h.set_weights(tpl_args, {
     h.set_weights(tpl_args, {
         prefix = 'generation_weight',
         prefix = 'generation_weight',
Line 363: Line 327:
     })
     })
      
      
     -- Validate and set mod sell values
     -- Validate and store mod sell values
     local i = 0
     local i = 0
     local names = {}
     local names = {}
Line 413: 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.display_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 447: 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(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 501: 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(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 551: 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(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 600: 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(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 638: 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