Path of Exile Wiki

Please consider helping keep the wiki up to date. Check the to-do list of updates needed for version 3.14.0.

Game data exports will becoming later as the technical changes in addition to regular changes take some more time.

READ MORE

Path of Exile Wiki
Advertisement
Template info icon Module documentation[view] [edit] [history] [purge]

The item module is used for handling item data and displaying it in various ways including infoboxes, inline links, and tables.

Usage

Each item has its own page that stores stats and other data pertaining to that item. This is accomplished by invoking the item function from this module.

<onlyinclude>{{#invoke:item|item
|name = Item Name

... pass in other arguments ...

}}</onlyinclude>

This displays an infobox for the item and sets up the item data for inclusion on other pages. See Wurm's Molt for an example of this setup.

Parameters

All parameters except for Name are optional. They can be excluded or left blank.

Parameter Description
Name Name of the item
SortKey The sort key for sorting the item in categories and tables – Defaults to Name
PageName Name of the item's wiki page – Defaults to Name
Image File name of the item graphic – Defaults to PageName.png
Size Size of the item in grid units – Possible values are, but not limited to: 1x1, 2x2, and 2x4. Defaults to a "best guess" based on the item's type, subtype, and requirements.
Rarity Rarity of the item – This only applies to weapons, armour, accessories, flasks, and maps. Possible values are: Normal, Magic, Rare, and Unique. Defaults to Normal.
Type Type of the item, for formatting purposes – Possible values are: Weapon, Armour, Accessory, Flask, Map, Currency, Quest and Microtrans.
Subtype Subtype of the item – Possible values are, but not limited to: Axe, Staff, Gloves, Body Armour, and Ring.
BaseItem Base item page name, for rares and uniques
RequiredLevel Level requirement of the item
RequiredStrength Strength requirement of the item
RequiredDexterity Dexterity requirement of the item
RequiredIntelligence Intelligence requirement of the item
PhysicalDamage Physical damage of the weapon
FireDamage Fire damage of the weapon
ColdDamage Cold damage of the weapon
LightningDamage Lightning damage of the weapon
ChaosDamage Chaos damage of the weapon
AverageDamage Override for normal average damage calculation; used for weapons such as Dyadus
CriticalStrikeChance Critical strike chance of the weapon
AttacksPerSecond Attacks per second of the weapon
Armour Armour rating of the armour item
Evasion Evasion rating of the armour item
EnergyShield Energy shield of the armour item
Block Block chance of the shield
MapLevel Map level of the map item
MapTier Map tier of the map item
ItemQuantity Item quantity modifier of the map item
StackSize Maximum stack size of the currency item
Effect Effect of the usable utility flask, currency item, or microtransaction item
FlaskLife Life restoration on use of the flask
FlaskMana Mana restoration on use of the flask
FlaskDuration Life or mana restoration time or utility effect duration of the flask
FlaskCharges Total "charge" capacity of the flask
FlaskChargesUsed "Charges" consumed on use of the flask
ImplicitMods Implicit mods of the item – Separate each line with <br>.
RandomMods Random mods of the item – Separate each line with <br>.
Cosmetic Cosmetic mods of the item – Separate each line with <br>.
FlavourText Flavour text of the item
HelpText Help text of the item

Numerical ranges

There are two types of numerical ranges for items: damage ranges and random value ranges. See the discussion about distinguishing between ranges.

Damage ranges are two numbers separated by an en dash (&ndash;), indicating a range of possible values for damage calculation. These appear on the wiki very similar to how they appear in game.

  • Physical Damage: 15–23
  • Adds 48–72 Fire Damage

Random value ranges are two numbers or damage ranges separated by the word "to" and enclosed in parentheses, indicating a range of possible values for item stat rolls. These do not appear in game at all, but are shown on the wiki for informational purposes.

  • Evasion Rating: (80 to 91)
  • +(60 to 80) to maximum Life
  • (10 to 20)% increased Attack Speed
  • Adds (15–25 to 24–35) Chaos Damage

local p = {}
local h = {}
local views = {}
local getArgs, roundAndPad
local xtable = require('Module:Table')

-- will be moved to a differnt module
local game = {}
game.attributes = {
    {
        long_upper = 'Strength',
        long_lower = 'strength',
        short_upper = 'Str',
        short_lower = 'str',
    }, 
    {
        long_upper = 'Dexterity',
        long_lower = 'dexterity',
        short_upper = 'Dex',
        short_lower = 'dex',
    }, 
    {
        long_upper = 'Intelligence',
        long_lower = 'intelligence',
        short_upper = 'Int',
        short_lower = 'int',
    },
}

game.damage_types = {
    {
        short_upper = 'Physical',
        short_lower = 'physical',
    },
    {
        short_upper = 'Fire',
        short_lower = 'fire',
    },
    {
        short_upper = 'Cold',
        short_lower = 'cold',
    },
    {
        short_upper = 'Lightning',
        short_lower = 'lightning',
    },
    {
        short_upper = 'Chaos',
        short_lower = 'chaos',
    },
}


---------------------------------------------------------------------
-- Implements {{item}}
---------------------------------------------------------------------
function p.item(frame)
    if not getArgs then
        getArgs = require('Module:Arguments').getArgs
    end
    local args = getArgs(frame, {
        parentFirst = true
    })
    args.view = args.view or args.View or 'full'
    if not views[args.view] then
        error('The requested view "' .. args.view .. '" does not exist')
    end
    args.format = args.format or args.Format
    args.name = args.name or args.Name
    args.sortKey = args.sortKey or args.SortKey or args.name
    args.pageName = args.pageName or args.PageName or args.name
    args.art = args.art or args.Art or args.pageName .. ' card art.png'
    args.size = args.size or args.Size or '2x_'
    args.rarity = args.rarity or args.Rarity or 'Normal'
    args.type = args.type or args.Type
    args.subtype = args.subtype or args.Subtype
    args.baseItem = args.baseItem or args.BaseItem
    args.baseItemPage = args.baseItemPage or args.BaseItemPage or args.baseItem
    args.level = args.level or args.RequiredLevel
    args.averageDamage = args.averageDamage or args.AverageDamage
    args.critChance = args.critChance or args.CriticalStrikeChance
    args.attacksPerSecond = args.attacksPerSecond or args.AttacksPerSecond
    args.block = args.block or args.blockChance or args.Block
    args.armour = args.armour or args.Armour
    args.evasion = args.evasion or args.Evasion
    args.energyshield = args.energyshield or args.energyShield or args.EnergyShield
    args.talismanTier = args.talismanTier or args.TalismanTier
    args.mapLevel = args.mapLevel or args.MapLevel
    args.itemQuantity = args.itemQuantity or args.itemQuantity
    args.radius = args.radius or args.Radius
    args.limit = args.limit or args.Limit
    args.stackSize = args.stackSize or args.StackSize
    args.effect = args.effect or args.Effect
    args.life = args.life or args.FlaskLife
    args.mana = args.mana or args.FlaskMana
    args.duration = args.duration or args.FlaskDuration
    args.chargeCap = args.chargeCap or args.FlaskCharges
    args.chargeUse = args.chargeUse or args.FlaskChargesUsed
    args.reward = args.reward or args.Reward
    args.drop = args.drop or args.Drop
    args.implicitMods = args.implicitMods or args.ImplicitMods
    args.randomMods = args.randomMods or args.RandomMods
    args.cosmeticMods = args.cosmeticMods or args.CosmeticMods
    args.helpText = args.helpText or args.HelpText
    args.flavourText = args.flavourText or args.FlavourText
    args.variations = args.variations or args.Variations
    args.variations = tonumber(args.variations)
    
    for _, attr in ipairs(game.attributes) do
        -- i.e. args['strength'] = args['strength'] or args['RequiredStrength']
        args[attr['long_lower']] = args[attr['long_lower']] or args['Required' .. attr['long_upper']]
    end
    
    for _, dtype in ipairs(game.damage_types) do
        -- i.e. args['physical'] = args['physicalDamage'] or args['PhysicalDamage']
        args[dtype['short_lower']] = args[dtype['short_lower'] .. 'Damage'] or args[dtype['short_upper'] .. 'Damage']
    end
    
    if args.type == 'Decoration' then
        args.image = args.image or args.Image or args.pageName .. ' Decoration.png'
        args.effect = args.effect or 'Creates an object in your hideout'
        args.helpText = args.helpText or 'Right click this item then left click a location on the ground to create the object'
    else
        args.image = args.image or args.Image or args.pageName .. '.png'
    end
    
    return views[args.view](args)
end

---------------------------------------------------------------------
-- Views
---------------------------------------------------------------------
function views.full(args)
    args.stats = h.statsBuilder(args)
    args.class = 'itembox-full'
    args.name = '[[' .. args.pageName .. '|' .. args.name .. ']]'
    args.baseItem = args.baseItem and ('[[' .. args.baseItemPage .. '|' .. args.baseItem .. ']]') or nil
    local types = {
        ['Weapon'] = {
            ['Bow'] = 'Bows',
            ['Claw'] = 'Claws',
            ['Dagger'] = 'Daggers',
            ['Wand'] = 'Wands',
            ['Fishing Rod'] = 'Fishing rods',
            ['Staff'] = 'Staves',
            ['One Handed Axe'] = 'One-handed axes',
            ['Two Handed Axe'] = 'Two-handed axes',
            ['One Handed Mace'] = 'One-handed maces',
            ['Two Handed Mace'] = 'Two-handed maces',
            ['One Handed Sword'] = 'One-handed swords',
            ['Two Handed Sword'] = 'Two-handed swords'
        },
        ['Armour'] = {
            ['Body Armour'] = 'Body armours',
            ['Helmet'] = 'Helmets',
            ['Shield'] = 'Shields',
            ['Boots'] = 'Boots',
            ['Gloves'] = 'Gloves'
        },
        ['Accessory'] = {
            ['Amulet'] = 'Amulets',
            ['Belt'] = 'Belts',
            ['Quiver'] = 'Quivers',
            ['Ring'] = 'Rings'
        },
        ['Flask'] = {
            ['Life'] = 'Life flasks',
            ['Mana'] = 'Mana flasks',
            ['Hybrid'] = 'Hybrid flasks',
            ['Utility'] = 'Utility flasks'
        },
        ['Map'] = {
            ['Fragment'] = 'Map fragments',
            ['_default'] = 'Maps'
        },
        ['Jewel'] = 'Jewels',
        ['Currency'] = 'Currency items',
        ['Card'] = 'Divination cards',
        ['Quest'] = 'Quest items',
        ['Microtrans'] = 'Microtransaction features',
        ['Decoration'] = 'Decorations'
    }
    local category = types[args.type]
    if type(category) == 'table' then
        category = types[args.type][args.subtype or '_default']
    end
    if category then
        if args.rarity == 'Unique' then
            category = 'Unique ' .. string.lower(category)
        end
    else
        category = 'Items with invalid types'
    end
    args.belowStats = mw.html.create('div')
    if args.type ~= 'Card' then
        args.belowStats
            :attr('class', 'itemboximage')
            :wikitext( '[[File:' .. args.image .. '|' .. p._itemsize({args.size}) .. '|]]' )
    end
    if mw.title.getCurrentTitle().fullText == args.pageName then
        args.belowStats
            :wikitext( '[[Category:' .. category .. ']]' )
    end
    return tostring( p._itembox(args) )
end

function views.standard(args)
    args.stats = h.statsBuilder(args)
    args.name = '[[' .. args.pageName .. '|' .. args.name .. ']]'
    args.baseItem = args.baseItem and ('[[' .. args.baseItemPage .. '|' .. args.baseItem .. ']]') or nil
    return tostring( p._itembox(args) )
end

function views.inline(args)
    args.stats = h.statsBuilder(args)
    args.class = 'itemboxhover itemboxhoverhide itemboxhovernojs'
    args.isHover = true
    args.itemLinkText = args.itemLinkText or args.name
    args.itemLinkArt = args.itemLinkArt and string.gsub(args.image, '%.(%a+)$', ' ' .. args.itemLinkArt .. '.%1') or args.image
    local container = mw.html.create('span')
        :tag('span')
            :attr('class', 'itemhover')
            :wikitext(
                args.itemLinkLarge
                    and '[[' .. args.pageName .. '|' .. args.itemLinkText .. ']] <br> [[File:' .. args.itemLinkArt .. '|' .. p._itemsize({args.size, '39'}) .. '|link=' .. args.pageName .. '|alt=]]'
                    or '[[File:' .. args.itemLinkArt .. '|16x16px|link=|alt=]][[' .. args.pageName .. '|' .. args.itemLinkText .. ']]'
            )
            :done()
        :node( p._itembox(args) )
    if args.type ~= 'Card' then
        container
            :tag('span')
                :attr('class', 'itemboxhovericon itemboxhoverhide itemboxhovernojs')
                :wikitext( '[[File:' .. args.itemLinkArt .. '|' .. p._itemsize({args.size}) .. '|link=|alt=]]' )
                :done()
    end
    return tostring(container)
end

function views.tablerow(args)
    if not roundAndPad then
        roundAndPad = require('Module:Decimals')._main
    end
    local otherPage = mw.title.getCurrentTitle().fullText ~= args.pageName
    local flags = args.flags and mw.text.split(args.flags, ' ', true) or {}
    for i=1, #flags do
        flags[ flags[i] ] = true
    end
    local function newNA(val)
        local cell = mw.html.create('td')
            :attr('class', 'table-na')
            :wikitext('<small>N/A</small>')
        if val ~= nil then
            cell
                :attr('data-sort-value', val)
        end
        return cell
    end
    local row = mw.html.create('tr')
    if otherPage then
        row
            :attr('id', args.name)
    end
    if flags.name then
        row
            :tag('td')
                :attr('data-sort-value', args.sortKey)
                :wikitext( (otherPage and '[[' .. args.pageName .. '|' .. args.name .. ']]' or args.name) .. ' <br> [[File:' .. args.image .. '|' .. p._itemsize({args.size, '39'}) .. '|link=' .. (otherPage and args.pageName or '') .. '|alt=]]' )
                :done()
    end
    if flags.base then
        row
            :tag('td')
                :attr('data-sort-value', args.baseItemPage)
                :wikitext( '[[' .. args.baseItemPage .. '|' .. args.baseItem .. ']]' )
                :done()
    end
    if flags.level then
        if args.level then
            row
                :tag('td')
                    :wikitext(args.level)
                    :done()
        else
            row
                :node( newNA(0) )
        end
    end
    if args.type == 'Weapon' or args.type == 'Armour' then
        for _,attr in ipairs(game.attributes) do
            if flags[attr['short_lower']] then
                if args[attr['long_lower']] then
                    row
                        :tag('td')
                            :wikitext(args[attr['long_lower']])
                            :done()
                else
                    row
                        :node( newNA(0) )
                end
            end
        end
    end
    if args.type == 'Weapon' then
        if flags.weapon then
            local avgDmg = tonumber(args.averageDamage)
            if avgDmg == nil then
                avgDmg = 0
                for _, dtype in ipairs(game.damage_types) do
                    avgDmg = avgDmg + (args[dtype['short_lower']] and p._itemRangeAverage{args[dtype['short_lower']]} or 0)
                end
            end
            local avgCrt = args.critChance and p._itemRangeAverage({args.critChance}) or 0
            local avgAps = args.attacksPerSecond and p._itemRangeAverage({args.attacksPerSecond}) or 0
            local damage = mw.html.create('td')
                :attr('data-sort-value', avgDmg)
            if avgDmg == 0 then
                damage
                    :wikitext('0')
            else
                for _, dtype in ipairs(game.damage_types) do
                    if args[dtype['short_lower']] then
                        damage
                            :node( h.newColor(dtype['short_lower'], args[dtype['short_lower']]) )
                            :tag('br'):done()
                    end
                end
            end
            row
                :node(damage)
                :tag('td')
                    :attr('data-sort-value', avgCrt)
                    :wikitext(args.critChance)
                    :done()
                :tag('td')
                    :attr('data-sort-value', avgAps)
                    :wikitext(args.attacksPerSecond)
                    :done()
                :tag('td')
                    :wikitext( roundAndPad( avgDmg * avgAps * (1 - avgCrt / 100) + avgDmg * avgAps * avgCrt / 100 * 1.5, 2 ) )
                    :done()
        end
    elseif args.type == 'Armour' then
        local _display = {
            'block',
            'armour',
            'evasion',
            'energyshield',
        }
        for _, v in ipairs(_display) do
            if flags[v] then
                if args[v] then
                    row
                        :tag('td')
                            :attr( 'data-sort-value', p._itemRangeAverage({args[v]}) )
                            :wikitext(args[v])
                            :done()
                else
                    row
                        :node( newNA(0) )
                end
            end
        end
    elseif args.type == 'Flask' then
        if flags.flask then
            if flags.life or flags.mana then
                if flags.life then
                    row
                        :tag('td')
                            :attr( 'data-sort-value', p._itemRangeAverage({args.life}) )
                            :wikitext(args.life)
                            :done()
                else
                    row
                        :node( newNA(0) )
                end
                if flags.mana then
                    row
                        :tag('td')
                            :attr( 'data-sort-value', p._itemRangeAverage({args.mana}) )
                            :wikitext(args.mana)
                            :done()
                else
                    row
                        :node( newNA(0) )
                end
                row
                    :tag('td')
                        :attr( 'data-sort-value', p._itemRangeAverage({args.duration}) )
                        :wikitext(args.duration)
                        :done()
            end
            if flags.effect then
                if args.effect then
                    row
                        :tag('td')
                            :attr('class', 'text-mod')
                            :node(args.effect)
                            :done()
                        :tag('td')
                            :attr( 'data-sort-value', p._itemRangeAverage({args.duration}) )
                            :wikitext(args.duration)
                            :done()
                else
                    row
                        :node( newNA() )
                        :node( newNA() )
                end
            end
            local avgChargeCap = p._itemRangeAverage({args.chargeCap})
            local avgChargeUse = p._itemRangeAverage({args.chargeUse})
            row
                :tag('td')
                    :attr( 'data-sort-value', avgChargeCap )
                    :wikitext(args.chargeCap)
                    :done()
                :tag('td')
                    :attr( 'data-sort-value', avgChargeUse )
                    :wikitext(args.chargeUse)
                    :done()
                :tag('td')
                    :wikitext( roundAndPad( avgChargeCap / avgChargeUse, 2 ) )
                    :done()
        end
    elseif args.type == 'Map' then
        if flags.map then
            row
                :tag('td')
                    :wikitext(args.mapLevel)
                    :done()
            if flags.qty then
                if args.itemQuantity then
                    row
                        :tag('td')
                            :attr( 'data-sort-value', p._itemRangeAverage({args.itemQuantity}) )
                            :wikitext(args.itemQuantity)
                            :done()
                else
                    row
                        :node( newNA(0) )
                end
            end
        end
    elseif args.type == 'Jewel' then
        if flags.jewel then
            if args.limit then
                row
                    :tag('td')
                        :wikitext(args.limit)
                        :done()
            else
                row
                    :node( newNA() )
            end
            if args.radius then
                row
                    :tag('td')
                        :wikitext(args.radius)
                        :done()
            else
                row
                    :node( newNA() )
            end
        end
    elseif args.type == 'Currency' then
        if flags.currency then
            if args.stackSize then
                row
                    :tag('td')
                        :wikitext(args.stackSize)
                        :done()
            else
                row
                    :node( newNA() )
            end
            if flags.effect then
                if args.effect then
                    row
                        :tag('td')
                            :attr('class', 'text-mod')
                            :wikitext(args.effect)
                            :done()
                else
                    row
                        :node( newNA() )
                end
            end
        end
    elseif args.type == 'Card' then       
        if flags.card then
            row
                :tag('td')
                    :wikitext(args.stackSize)
                    :done()
                :tag('td')
                    :tag('div')
                        :attr('class', 'itemboxstats')
                        :wikitext(args.reward)
                        :done()
                    :done()
                :tag('td')
                    :wikitext(args.drop)
                    :done()
        end
    end
    if flags.mods then
        if args.implicitMods or args.randomMods or args.cosmeticMods then
            local stats = mw.html.create('div')
                :attr('class', 'itemboxstats')
                
            local _display = {
                'implicitMods',
                'randomMods',
                'cosmeticMods',
            }
            
            for _, v in ipairs(_display) do
                if args[v] then
                    stats
                        :node(
                            h.newGroup('text-mod')
                                :wikitext(args[v])
                        )
                end
            end
                
            row
                :tag('td')
                    :node(stats)
                    :done()
        else
            row
                :node( newNA() )
        end
    end
    if flags.flavour then
        if args.flavourText then
            row
                :tag('td')
                    :attr('class', 'text-flavour')
                    :wikitext(args.flavourText)
                    :done()
        else
            row
                :node( newNA() )
        end
    end
    if flags.help then
        if args.helpText then
            row
                :tag('td')
                    :attr('class', 'text-help')
                    :wikitext(args.helpText)
                    :done()
        else
            row
                :node( newNA() )
        end
    end
    return row
end

---------------------------------------------------------------------
-- Implements {{itembox}}
---------------------------------------------------------------------
function p.itembox(frame)
    if not getArgs then
        getArgs = require('Module:Arguments').getArgs
    end
    local args = getArgs(frame, {
        wrappers = 'Template:Itembox'
    })
    return p._itembox(args)
end
function p._itembox(args)
    local frames = {
        ['Currency'] = 'currency',
        ['Microtrans'] = 'currency',
        ['Gem'] = 'gem',
        ['Quest'] = 'quest',
        ['Card'] = 'divicard'
    }
    local container = mw.html.create(args.isHover and 'span' or 'div')
        :attr( 'class', 'itembox-' .. ( string.lower(args.frame or frames[args.type] or args.rarity or 'normal') ) .. ' ' .. (args.class or '') )
    if type(args.aboveStats) == 'table' then
        container:node(args.aboveStats)
    elseif type(args.aboveStats) == 'string' then
        container:wikitext(args.aboveStats)
    end
    if args.type == 'Card' then
        container
            :tag('span')
                :attr( 'class', 'divicard-wrapper')
                :tag('span')
                    :attr('class', 'divicard-art')
                    :wikitext( '[[File:' .. args.art .. '|link=|alt=]]' )
                    :done()
                :tag('span')
                    :attr('class', 'divicard-frame')
                    :wikitext( '[[File:Divination card frame.png|link=|alt=]]' )
                    :done()
                :tag('span')
                    :attr('class', 'divicard-header')
                    :wikitext(args.name)
                    :done()
                :tag('span')
                    :attr('class', 'divicard-stack')
                    :wikitext(args.stackSize)
                    :done()
                :tag('span')
                    :attr('class', 'divicard-reward')
                    :tag('span')
                        :wikitext(args.reward)
                        :done()
                    :done()
                :tag('span')
                    :attr('class', 'divicard-flavour text-flavour')
                    :tag('span')
                        :wikitext(args.flavourText)
                        :done()
                    :done()
                :done()
    else
        container
            :tag('span')
                :attr( 'class', 'itemboxheader-' .. (args.baseItem and 'double' or 'single') )
                :tag('span')
                    :attr('class', 'itemboxheaderleft')
                    :done()
                :tag('span')
                    :attr('class', 'itemboxheaderright')
                    :done()
                :tag('span')
                    :attr('class', 'itemboxheadertext')
                    :wikitext( args.name .. (args.baseItem and '<br>' .. args.baseItem or '') )
                    :done()
                :done()
        local stats = mw.html.create('span')
            :attr('class', 'itemboxstats')
        if type(args.stats) == 'table' then
            stats:node(args.stats)
        elseif type(args.stats) == 'string' then
            stats:wikitext(args.stats)
        end
        container:node(stats)
    end
    if type(args.belowStats) == 'table' then
        container:node(args.belowStats)
    elseif type(args.belowStats) == 'string' then
        container:wikitext(args.belowStats)
    end
    return container
end

---------------------------------------------------------------------
-- Implements {{il}}
---------------------------------------------------------------------
function p.itemLink(frame)
    if not getArgs then
        getArgs = require('Module:Arguments').getArgs
    end
    local args = getArgs(frame, {
        parentOnly = true
    })
    local save = 'itemlink__' .. string.gsub(args[1], '%W+', '') .. '_' .. (args.art or args.image or '1') .. (args.large and '_large' or '')
    if frame:callParserFunction('#varexists', {save}) == '1' then
        return frame:callParserFunction('#var', {save})
    end
    return frame:callParserFunction('#vardefineecho', save, frame:expandTemplate{
        title = ':' .. args[1],
        args = {
            view = 'inline',
            itemLinkText = args[2],
            itemLinkArt = args.art or args.image,
            itemLinkLarge = args.large
        }
    })
end

---------------------------------------------------------------------
-- Implements {{item table}}
---------------------------------------------------------------------
function p.itemTable(frame)
    if not getArgs then
        getArgs = require('Module:Arguments').getArgs
    end
    local args = getArgs(frame, {
        parentOnly = true
    })
    args.flags = args.flags or args.format
    local flags = args.flags and mw.text.split(args.flags, ' ', true) or {}
    for i=1, #flags do
        flags[ flags[i] ] = true
    end
    local function newAbbr(text, title)
        return mw.html.create('abbr')
            :attr('class', 'nounderline')
            :attr('title', title)
            :wikitext(text)
    end
    local tbl = mw.html.create('table')
        :attr('class', 'wikitable sortable')
        :attr('style', 'text-align:center; ' .. ( args.width and 'width:' .. args.width or '' ) )
    local hdr = mw.html.create('tr')
        :tag('th')
            :wikitext('Name')
            :done()
    if flags.base then
        hdr
            :tag('th')
                :wikitext('Base Item')
                :done()
    end
    if flags.level then
        hdr
            :tag('th')
                :attr('data-sort-type', 'number')
                :node( newAbbr('[[File:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level') )
                :done()
    end
    for _, attr in ipairs(game.attributes) do
        if flags[attr['short_lower']] then
            hdr
                :tag('th')
                    :attr('data-sort-type', 'number')
                    :node( newAbbr('[[File:' .. attr['long_upper'] .. 'Icon_small.png|link=|' .. attr['short_upper'] .. '.]]', 'Required ' .. attr['long_upper']) )
                    :done()
        end
    end
    if flags.weapon then
        hdr
            :tag('th')
                :attr('data-sort-type', 'number')
                :wikitext('Damage')
                :done()
            :tag('th')
                :attr('data-sort-type', 'number')
                :wikitext('Critical Strike Chance')
                :done()
            :tag('th')
                :attr('data-sort-type', 'number')
                :wikitext('Attacks per Second')
                :done()
            :tag('th')
                :attr('data-sort-type', 'number')
                :wikitext('Damage per Second')
                :done()
    end
    if flags.block then
        hdr
            :tag('th')
                :attr('data-sort-type', 'number')
                :wikitext('Chance to Block')
                :done()
    end
    if flags.armour then
        hdr
            :tag('th')
                :attr('data-sort-type', 'number')
                :wikitext('Armour Rating')
                :done()
    end
    if flags.evasion then
        hdr
            :tag('th')
                :attr('data-sort-type', 'number')
                :wikitext('Evasion Rating')
                :done()
    end
    if flags.energyshield then
        hdr
            :tag('th')
                :attr('data-sort-type', 'number')
                :wikitext('Energy Shield')
                :done()
    end
    if flags.flask then
        if flags.life or flags.mana then
            if flags.life then
                hdr
                    :tag('th')
                        :attr('data-sort-type', 'number')
                        :node( newAbbr('Life', 'Life Recovered over Time per Sip') )
                        :done()
            end
            if flags.mana then
                hdr
                    :tag('th')
                        :attr('data-sort-type', 'number')
                        :node( newAbbr('Mana', 'Mana Recovered over Time per Sip') )
                        :done()
            end
            hdr
                :tag('th')
                    :attr('data-sort-type', 'number')
                    :node( newAbbr(
                        'Time', 
                        (flags.life and 'Life' or '') .. ( (flags.life and flags.mana) and ' and ' or '' ) .. (flags.mana and 'Mana' or '') .. ' Recovery Time in Seconds'
                    ) )
                    :done()
        end
        if flags.effect then
            hdr
                :tag('th')
                    :attr('class', 'unsortable')
                    :wikitext('Effects')
                    :done()
                :tag('th')
                    :attr('data-sort-type', 'number')
                    :wikitext('Duration')
                    :done()
        end
        hdr
            :tag('th')
                :attr('data-sort-type', 'number')
                :node( newAbbr('Capacity', 'Number of Charges the Flask Can Hold') )
                :done()
            :tag('th')
                :attr('data-sort-type', 'number')
                :node( newAbbr('Usage', 'Number of Charges Used per Sip') )
                :done()
            :tag('th')
                :attr('data-sort-type', 'number')
                :node( newAbbr('Sips', 'Number of Sips at Maximum Capacity') )
                :done()
    end
    if flags.map then
        hdr
            :tag('th')
                :attr('data-sort-type', 'number')
                :wikitext('Map Level')
                :done()
        if flags.qty then
            hdr
                :tag('th')
                    :attr('data-sort-type', 'number')
                    :wikitext('Item Quantity')
                    :done()
        end
    end
    if flags.jewel then
        hdr
            :tag('th')
                :wikitext('Limit')
                :done()
        hdr
            :tag('th')
                :wikitext('Radius')
                :done()
    end
    if flags.currency then
        hdr
            :tag('th')
                :attr('data-sort-type', 'number')
                :wikitext('Stack Size')
                :done()
        if flags.effect then
            hdr
                :tag('th')
                    :attr('class', 'unsortable')
                    :wikitext('Effects')
                    :done()
        end
    end
    if flags.card then
        hdr
            :tag('th')
                :attr('data-sort-type', 'number')
                :node( newAbbr('Set', 'Amount needed to complete a set') )
                :done()
            :tag('th')
                :attr('class', 'unsortable')
                :wikitext('Reward')
                :done()
            :tag('th')
                :attr('class', 'unsortable')
                :wikitext('Drop Restrictions')
                :done()
    end
    if flags.mods then
        hdr
            :tag('th')
                :attr('class', 'unsortable')
                :wikitext('Modifiers')
                :done()
    end
    if flags.flavour then
        hdr
            :tag('th')
                :attr('class', 'unsortable')
                :wikitext('Flavour Text')
                :done()
    end
    if flags.help then
        hdr
            :tag('th')
                :attr('class', 'unsortable')
                :wikitext('Help Text')
                :done()
    end
    tbl:node(hdr)
    local i = 1
    while args[i] do
        tbl
            :node(
                frame:expandTemplate{
                    title = ':' .. args[i],
                    args = {
                        view = 'tablerow',
                        flags = 'name ' .. args.flags
                    }
                }
            )
        i = i + 1
    end
    return tostring(tbl)
end

---------------------------------------------------------------------
-- Implements {{item range average}}
---------------------------------------------------------------------
function p.itemRangeAverage(frame)
    if not getArgs then
        getArgs = require('Module:Arguments').getArgs
    end
    local args = getArgs(frame, {
        wrappers = 'Template:Item range average'
    })
    return p._itemRangeAverage(args)
end
function p._itemRangeAverage(args)
    if args[1] == nil then
        return nil
    end
    local s = {}
    s.mod = string.match(args[1], '>([^><]+)</') or args[1]
    s.sign = string.find(s.mod, '&minus;', 0, true) and -1 or 1
    s.min = string.match(s.mod, '%((%S+)%s*to') or s.mod
    s.max = string.match(s.mod, 'to%s*(%S+)%)') or s.min
    s.minLow = tonumber( string.match(s.min, '(%d*%.?%d+)') )
    s.minHigh = tonumber( string.match(s.min, '&ndash;(%d*%.?%d+)') or s.minLow )
    s.maxLow = tonumber( string.match(s.max, '(%d*%.?%d+)') )
    s.maxHigh = tonumber( string.match(s.max, '&ndash;(%d*%.?%d+)') or s.maxLow )
    if #{s.minLow, s.minHigh, s.maxLow, s.maxHigh} == 0 then
        return nil
    end
    return ( ( (s.minLow + s.minHigh) / 2 ) + ( (s.maxLow + s.maxHigh) / 2 ) ) / 2 * s.sign
end

---------------------------------------------------------------------
-- Implements {{itemsize}}
---------------------------------------------------------------------
function p.itemsize(frame)
    if not getArgs then
        getArgs = require('Module:Arguments').getArgs
    end
    local args = getArgs(frame, {
        wrappers = 'Template:Itemsize'
    })
    return p._itemsize(args)
end
function p._itemsize(args)
    local size = args[1] or '1x_'
    local grid = args[2] or 78
    local dim = mw.text.split(size, 'x', true)
    if dim[1] == '_' then
        if dim[2] == '_' then
            dim[1] = grid
        else
            dim[1] = ''
        end
    else
        dim[1] = dim[1] * grid
    end
    if dim[2] == '_' then
        dim[2] = ''
    else
        dim[2] = 'x' .. dim[2] * grid
    end
    return dim[1] .. dim[2] .. 'px'
end

---------------------------------------------------------------------
-- Helper functions
---------------------------------------------------------------------
function h.statsBuilder(args)
    local container = mw.html.create('span')
    local group
    if args.type == 'Weapon' then
        group = h.newGroup()
            :wikitext(args.subtype)
            :tag('br'):done()
        if args.physical then
            group
                :wikitext('Physical Damage: ')
                :node( h.newColor('value', args.physical) )
                :tag('br'):done()
        end
        if args.fire or args.cold or args.lightning then
            local elementalDamage = {}
            if args.fire then
                table.insert( elementalDamage, tostring( h.newColor('fire', args.fire) ) )
            end
            if args.cold then
                table.insert( elementalDamage, tostring( h.newColor('cold', args.cold) ) )
            end
            if args.lightning then
                table.insert( elementalDamage, tostring( h.newColor('lightning', args.lightning) ) )
            end
            group
                :wikitext('Elemental Damage: ')
                :wikitext( table.concat(elementalDamage, ', ') )
                :tag('br'):done()
        end
        if args.chaos then
            group
                :wikitext('Chaos Damage: ')
                :node( h.newColor('chaos', args.chaos) )
                :tag('br'):done()
        end
        group
            :wikitext('Critical Strike Chance: ')
            :node( h.newColor('value', args.critChance) )
            :tag('br'):done()
            :wikitext('Attacks per Second: ')
            :node( h.newColor('value', args.attacksPerSecond) )
        container:node(group)
    elseif args.type == 'Armour' then
        if args.block or args.armour or args.evasion or args.energyshield then
            group = h.newGroup()
            if args.block then
                group
                    :wikitext('Chance to Block: ')
                    :node( h.newColor('value', args.block) )
                    :tag('br'):done()
            end
            if args.armour then
                group
                    :wikitext('Armour: ')
                    :node( h.newColor('value', args.armour) )
                    :tag('br'):done()
            end
            if args.evasion then
                group
                    :wikitext('Evasion: ')
                    :node( h.newColor('value', args.evasion) )
                    :tag('br'):done()
            end
            if args.energyshield then
                group
                    :wikitext('Energy Shield: ')
                    :node( h.newColor('value', args.energyshield) )
            end
            container:node(group)
        end
    elseif args.type == 'Map' then
        if args.mapLevel then
            group = h.newGroup()
                :wikitext('Map Level: ')
                :node( h.newColor('value', args.mapLevel) )
                :tag('br'):done()
            if args.itemQuantity then
                group
                    :wikitext('Item Quantity: ')
                    :node( h.newColor('value', args.itemQuantity) )
            end
            container:node(group)
        end
    elseif args.type == 'Jewel' then
        if args.limit or args.radius then
            group = h.newGroup()
            if args.limit then
                group
                    :wikitext('Limited to: ')
                    :node( h.newColor('value', args.limit) )
            end
            if args.radius then
                group
                    :wikitext('Radius: ')
                    :node( h.newColor('value', args.radius) )
            end
            container:node(group)
        end
    elseif args.type == 'Currency' then
        group = h.newGroup()
            :wikitext('Stack Size: ')
            :node( h.newColor('value', args.stackSize) )
        container:node(group)
        if args.effect then
            group = h.newGroup('textwrap text-mod')
                :wikitext(args.effect)
            container:node(group)
        end
    elseif args.type == 'Flask' then
        group = h.newGroup()
        if args.life then
            group
                :wikitext('Recovers ')
                :node( h.newColor('value', args.life) )
                :wikitext(' Life over ')
                :node( h.newColor('value', args.duration) )
                :wikitext(' Seconds')
                :tag('br'):done()
        end
        if args.mana then
            group
                :wikitext('Recovers ')
                :node( h.newColor('value', args.mana) )
                :wikitext(' Mana over ')
                :node( h.newColor('value', args.duration) )
                :wikitext(' Seconds')
                :tag('br'):done()
        end
        if args.effect then
            group
                :wikitext('Lasts ')
                :node( h.newColor('value', args.duration) )
                :wikitext(' Seconds')
                :tag('br'):done()
        end
        group
            :wikitext('Consumes ')
            :node( h.newColor('value', args.chargeUse) )
            :wikitext(' of ')
            :node( h.newColor('value', args.chargeCap) )
            :wikitext(' Charges on use')
            :tag('br'):done()
        if args.effect then
            group
                :node( h.newColor('mod', args.effect) )
        end
        container:node(group)
    elseif args.type == 'Microtrans' or args.type == 'Decoration' then
        group = h.newGroup()
        if args.subtype or args.stackSize then
            group
                :wikitext(args.subtype)
                :tag('br'):done()
            if args.stackSize then
                group
                    :wikitext('Stack Size: ')
                    :node( h.newColor('value', args.stackSize) )
            end
            container:node(group)
        end
        group = h.newGroup('textwrap text-mod')
            :wikitext(args.effect)
        container:node(group)
        
        if args.variations ~= nil then
            local variations_text = nil
            if args.variations == 1 then
                variations_text = '1 Variation'
            elseif args.variations > 1 then
                variations_text = args.variations .. ' Variations'
            end
            group = h.newGroup('textwrap text-mod')
                :wikitext(variations_text)
            container:node(group)
        end
    end
    if args.level or args.strength or args.dexterity or args.intelligence or args.talismanTier then
        local requirements = {}
        local attrLabel
        local use_short_label
        if args.level then
            table.insert( requirements, 'Level ' .. tostring( h.newColor('value', args.level) ) )
        end
        for _, attr in ipairs(game.attributes) do
            if args[attr['long_lower']] then
                use_short_label = false or args.level
                for _, attr2 in ipairs(game.attributes) do
                    if attr ~= attr2 then
                        use_short_label = use_short_label or args[attr2['long_lower']]
                    end
                end
                if use_short_label then
                    attrLabel = attr['short_upper']
                else
                    attrLabel = attr['long_upper']
                end
                table.insert( requirements, tostring( h.newColor('value', args[attr['long_lower']]) ) .. ' ' .. attrLabel )
            end
        end
        group = h.newGroup()
        if args.level or args.strength or args.dexterity or args.intelligence then
            group
                :wikitext('Requires ')
                :wikitext( table.concat(requirements, ', ') )
                :tag('br'):done()
        end
        if args.talismanTier then
            group
                :wikitext('Talisman Tier: ')
                :node( h.newColor('value', args.talismanTier) )
                :tag('br'):done()
        end
        container:node(group)
    end
    
    local _display = {
        {
            id = 'implicitMods',
            cls = 'text-mod',
        },
        {
            id = 'randomMods',
            cls = 'text-mod',
        },
        {
            id = 'cosmeticMods',
            cls = 'text-cosmetic',
        },
        {
            id = 'flavourText',
            cls = 'textwrap text-flavour',
        },
        {
            id = 'helpText',
            cls = 'textwrap text-help',
        },
    }
    for _, disp in ipairs(_display) do
        if args[disp['id']] then
            group = h.newGroup(disp['cls'])
                :wikitext(args[disp['id']])
            container:node(group)
        end
    end
    
    return container
end

function h.newColor(label, text)
    if text == nil or text == '' then
        return nil
    end
    return mw.html.create('span')
        :attr('class', 'text-' .. label)
        :wikitext(text)
end

function h.newGroup(class)
    return mw.html.create('span')
        :attr( 'class', 'itemboxstatsgroup ' .. (class or '') )
end

return p
Advertisement