Module:Sandbox: Difference between revisions

From Path of Exile 2 Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 192: Line 192:
         category = 'Items with invalid types'
         category = 'Items with invalid types'
     end
     end
     if args.type == 'Card' then
     args.belowStats = mw.html.create('div')
        args.belowStats = '[[Category:' .. category .. ']]'
    if args.type ~= 'Card' then
    else
         args.belowStats
         args.belowStats = mw.html.create('div')
             :attr('class', 'itemboximage')
             :attr('class', 'itemboximage')
             :wikitext( '[[File:' .. args.image .. '|' .. p._itemsize({args.size}) .. '|]]' )
             :wikitext( '[[File:' .. args.image .. '|' .. p._itemsize({args.size}) .. '|]]' )
    end
    if mw.title.getCurrentTitle().fullText == args.pageName then
        args.belowStats
             :wikitext( '[[Category:' .. category .. ']]' )
             :wikitext( '[[Category:' .. category .. ']]' )
     end
     end

Revision as of 06:25, 27 February 2016

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


This page is not an actual Scribunto module. It exists to provide editors a place to create experimental modules.

Naming your modules

To keep things tidy, please use the following format to name your experimental modules:

Module:Sandbox/Your username/Module name

Cleaning up unused modules

Experimental modules may be deleted by admins upon request or after a long period of inactivity.

List of modules in this area

For a list of the experimental modules under Module:Sandbox, see Special:PrefixIndex/Module:Sandbox/.

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
                local _display = {
                    'life',
                    'mana',
                } 
                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()
                               :tag('td')
                                    :attr( 'data-sort-value', p._itemRangeAverage({args.duration}) )
                                    :wikitext(args.duration)
                                    :done()
                        else
                            row
                                :node( newNA(0) )
                                :node( newNA(0) )
                        end
                    end
                end
            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