Module:Item2/core: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
(I thought prophecies were their own item class)
(This stat does not remove the level requirement)
(96 intermediate revisions by 3 users not shown)
Line 8: Line 8:
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('Item2')
 
local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or mw.loadData('Module:Game')


-- The cfg table contains all localisable strings and configuration, to make it
-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
-- easier to port this module to another wiki.
local cfg = mw.loadData('Module:Item2/config')
local cfg = use_sandbox and mw.loadData('Module:Item2/config/sandbox') or mw.loadData('Module:Item2/config')


local i18n = cfg.i18n
local i18n = cfg.i18n
Line 22: Line 25:
local h = {}
local h = {}


function h.process_mod_stats(tpl_args, args)
-- ----------------------------------------------------------------------------
     local lines = {}
-- Core
      
-- ----------------------------------------------------------------------------
     local skip = cfg.class_specifics[tpl_args.class_id]
 
     if skip then
local core = {}
        skip = skip.skip_stat_lines
 
     end
core.factory = {}
      
 
     local random_mods = {}
function core.factory.infobox_line(args)
      
     -- args: table
    for _, modinfo in ipairs(tpl_args._mods) do
    --  type: How to read data from tpl_args using the given keys. nil = Regular, gem = Gem progression, stat = Stats
         if modinfo.is_implicit == args.is_implicit then
    --  parts: table
             if modinfo.is_random == true then
     --  [n]: table
                if random_mods[modinfo.stat_text] then
     --    key: key to use. If type = gem and table is given, parse for subfield along path
                    table.insert(random_mods[modinfo.stat_text], modinfo)
    --    hide: Hide part if this function returns true
                 else
    --    hide_key: Alternate key to use to retrieve the value
                    random_mods[modinfo.stat_text] = {modinfo}
    --    hide_default: hide the value if this is set
                end
    --    hide_default_key: key to use if it isn't equal to the key parameter
             else
    --    ----- params from m_util.html.format_value -----
                 if modinfo.id == nil then
    --    func: Function to transform the value retrieved from the database
                     table.insert(lines, modinfo.result)
    --    fmt: Format string (or function that returns format string) to use for the value.
                -- Allows the override of the SMW fetched mod texts for this modifier via <modtype><id>_text parameter
    --      Default: '%s'
                elseif modinfo.text ~= nil then
    --    fmt_range: Format string to use for range value.
                    table.insert(lines, modinfo.text)
    --      Default: '(%s-%s)'
                else
    --    color: poe_color code to use for the value. False for no color.
                     for _, line in ipairs(m_util.string.split(modinfo.result['mods.stat_text'] or '', '<br>')) do
     --      Default: 'value' if value is unmodified; 'mod' if modified
                         if line ~= '' then
    --    class: Additional css class added to color tag
                             if skip == nil then
    --    inline: Format string to use for the output
                                table.insert(lines, line)
    --    inline_color: poe_color code to use for the output. False for no color.
                             else
    --      Default: Inherits from value color
                                local skipped = false
    --    inline_class: Additional css class added to inline color tag
                                for _, pattern in ipairs(skip) do
    --  sep: If specified, parts are joined with this separator before being formatted for output
                                    if string.match(line, pattern) then
    --  fmt: Format string to use for output. If not specified, parts are simply concatenated
                                        skipped = true
     --  color: poe_color code to use for output. Default: no color
                                        break
     --  class: Additional css class added to output
                                     end
 
                                end
     args.parts = args.parts or {}
                                if not skipped then
     return function (tpl_args)
                                    table.insert(lines, line)
        local base_values = {}
                                end
        local temp_values = {}
                             end
         if args.type == 'gem' then
             -- Skill progression. Look for keys in tpl_args.skill_levels
            if not cfg.class_groups.gems.keys[tpl_args.class_id] then
                 -- Skip if this item is not actually a gem
                return
            end
             for i, data in ipairs(args.parts) do
                 if data.key then
                     local path = type(data.key) == 'table' and data.key or {data.key}
                    -- Check for static value
                    local value = tpl_args.skill_levels[0]
                     for _, p in ipairs(path) do -- Parse for subfield along path
                         if value[p] == nil then
                             value = nil
                            break
                        else
                            value = value[p]
                        end
                    end
                    if value ~= nil then
                        base_values[i] = value
                        temp_values[#temp_values+1] = {value={min=value,max=value}, index=i}
                    else -- Check for leveled values
                        value = {
                            min = tpl_args.skill_levels[1],
                             max = tpl_args.skill_levels[tpl_args.max_level],
                        }
                        for k, _ in pairs(value) do
                            for _, p in ipairs(path) do -- Parse for subfield along path
                                if value[k][p] == nil then
                                    value[k] = nil
                                    break
                                else
                                     value[k] = value[k][p]
                                end
                            end
                        end
                        if value.min ~= nil and value.max ~= nil then
                            base_values[i] = value.min
                             temp_values[#temp_values+1] = {value=value, index=i}
                         end
                         end
                     end
                     end
                 end
                 end
             end
             end
         end
         elseif args.type == 'stat' then
    end
            -- Stats. Look for key in tpl_args._stats
   
            for i, data in ipairs(args.parts) do
    for stat_text, modinfo_list in pairs(random_mods) do
                local stat = tpl_args._stats[data.key]
        local text = {}
                if stat then
        for _, modinfo in ipairs(modinfo_list) do
                    local total = {min=0, max=0}
            table.insert(text, modinfo.result['mods.stat_text'])
                    for _, v in ipairs(stat) do
        end
                        if v.mod == nil or v.mod.is_implicit or v.mod.is_explicit then
   
                            total.min = total.min + v.min
        local tbl = mw.html.create('table')
                            total.max = total.max + v.max
        tbl
                        end
            :attr('class', 'random-modifier-stats mw-collapsed')
                     end
            :attr('style', 'text-align: left')
                     base_values[i] = total.min
            :tag('tr')
                     temp_values[#temp_values+1] = {value=total, index=i}
                :tag('th')
                end
                     :attr('class', 'mw-customtoggle-31')
            end
                     :wikitext(stat_text)
        else
                    :done()
            -- Regular. Look for key exactly as written in tpl_args
                :done()
            for i, data in ipairs(args.parts) do
            :tag('tr')
                local value = {}
                :attr('class', 'mw-collapsible mw-collapsed')
                if tpl_args[data.key .. '_range_minimum'] ~= nil then
                :attr('id', 'mw-customcollapsible-31')
                    value.min = tpl_args[data.key .. '_range_minimum']
                :tag('td')
                    value.max = tpl_args[data.key .. '_range_maximum']
                    :wikitext(table.concat(text, '<hr style="width: 20%">'))
                elseif tpl_args[data.key] ~= nil then
                     :done()
                    value.min = tpl_args[data.key]
                :done()
                    value.max = tpl_args[data.key]
        table.insert(lines, tostring(tbl))
                end
    end
                 if value.min ~= nil and value.max ~= nil then
   
                     base_values[i] = value.min
    if #lines == 0 then
                     temp_values[#temp_values+1] = {value=value, index=i}
        return
                end
    else
            end
        return table.concat(lines, '<br>')
        end
    end
       
end
        local final_values = {}
 
        for i, data in ipairs(temp_values) do
--  
            local opt = args.parts[data.index]
-- Factory
            local hide = false
--
            if type(opt.hide) == 'function' then
 
                local v = data.value
h.factory = {}
                 if opt.hide_key then
 
                     v = {
function h.factory.cast_text(k, args)
                         min = tpl_args[opt.hide_key .. '_range_minimum'],
    args = args or {}
                         max = tpl_args[opt.hide_key .. '_range_maximum'],
    return function (tpl_args, frame)
        tpl_args[args.key_out or k] = m_util.cast.text(tpl_args[k])
    end
end
 
-- ----------------------------------------------------------------------------
-- Core
-- ----------------------------------------------------------------------------
 
local core = {}
 
core.factory = {}
 
function core.factory.infobox_line(args)
    --[[
    args:
    type: How to read data from tpl_args using the given keys. nil = Regular, gem = Skill progression, stat = Stats
    parts:
      [n]:
      key: key to use
      allow_zero: allow zero values
      hide_default: hide the value if this is set
      hide_default_key: key to use if it isn't equal to the key parameter
      truncate: set to true to truncate the line
      -- from m_util.html.format_value --
      func: Function to transform the value retrieved from the database
      fmt: Format string (or function that returns format string) to use for the value. Default: '%s'
      fmt_range: Format string to use for the value range. Default: '(%s-%s)'
      color: poe_color code to use for the value range. False for no color. Default: 'mod'
      class: Additional css class added to color tag
      inline: Format string to use for the output
      inline_color: poe_color code to use for the output. False for no color. Default: 'default'
      inline_class: Additional css class added to inline color tag
    sep: If specified, parts are joined with this separator before being formatted for output
    fmt: Format string to use for output. If not specified, parts are simply concatenated
    color: poe_color code to use for output. Default: no color
    class: Additional css class added to output
    --]]
 
    args.parts = args.parts or {}
    return function (tpl_args, frame)
        local base_values = {}
        local temp_values = {}
        if args.type == 'gem' then
            -- Skill progression. Look for keys in tpl_args.skill_levels
            if not cfg.class_groups.gems.keys[tpl_args.class_id] then
                -- Skip if this item is not actually a gem
                return
            end
            for i, data in ipairs(args.parts) do
                local value = tpl_args.skill_levels[0][data.key]
                 if value ~= nil then
                     base_values[i] = value
                     temp_values[#temp_values+1] = {value={min=value,max=value}, index=i}
                 else
                     value = {
                         min=tpl_args.skill_levels[1][data.key],
                         max=tpl_args.skill_levels[tpl_args.max_level][data.key],  
                     }
                     }
                     if value.min == nil or value.max == nil then
                     if v.min == nil or v.max == nil then
                    else
                         v = tpl_args[opt.hide_key]
                         base_values[i] = value.min
                        temp_values[#temp_values+1] = {value=value, index=i}
                     end
                     end
                 end
                 end
            end
                hide = opt.hide(tpl_args, v)
        elseif args.type == 'stat' then
            elseif opt.hide_default ~= nil then
            -- Stats. Look for key in tpl_args._stats
                if opt.hide_default_key then
            for i, data in ipairs(args.parts) do
                     local v = {
                local value = tpl_args._stats[data.key]
                        min = tpl_args[opt.hide_default_key .. '_range_minimum'],
                if value ~= nil then
                        max = tpl_args[opt.hide_default_key .. '_range_maximum'],
                    base_values[i] = value.min
                    }
                     temp_values[#temp_values+1] = {value=value, index=i}
                    if v.min == nil or v.max == nil then
                end
                        if opt.hide_default == tpl_args[opt.hide_default_key] then
            end
                            hide = true
        else
                        end
            -- Regular. Look for key exactly as written in tpl_args
                    elseif opt.hide_default == v.min and opt.hide_default == v.max then
            for i, data in ipairs(args.parts) do
                        hide = true
                base_values[i] = tpl_args[data.key]
                    end
                local value = {}
                if tpl_args[data.key .. '_range_minimum'] ~= nil then
                    value.min = tpl_args[data.key .. '_range_minimum']
                    value.max = tpl_args[data.key .. '_range_maximum']
                elseif tpl_args[data.key] ~= nil then
                    value.min = tpl_args[data.key]
                    value.max = tpl_args[data.key]
                end
                if value.min == nil then
                 else
                 else
                     temp_values[#temp_values+1] = {value=value, index=i}
                     local v = data.value
                    if opt.hide_default == v.min and opt.hide_default == v.max then
                        hide = true
                    end
                 end
                 end
            end           
            if not hide then
                table.insert(final_values, data)
             end
             end
        end
         end
       
          
        local final_values = {}
         -- all zeros = dont display and return early
        for i, data in ipairs(temp_values) do
         if #final_values == 0 then
            local opt = args.parts[data.index]
             return nil
            local insert = false
            if opt.hide_default == nil then
                insert = true
            elseif opt.hide_default_key == nil then
                local v = data.value
                if opt.hide_default ~= v.min and opt.hide_default ~= v.max then
                    insert = true
                end
            else
                local v = {
                    min = tpl_args[opt.hide_default_key .. '_range_minimum'],
                    max = tpl_args[opt.hide_default_key .. '_range_maximum'],
                }
                if v.min == nil or v.max == nil then
                    if opt.hide_default ~= tpl_args[opt.hide_default_key] then
                        insert = true
                    end
                elseif opt.hide_default ~= v.min and opt.hide_default ~= v.max then
                    insert = true
                end
            end
           
            if insert == true then
                table.insert(final_values, data)
            end
         end
          
         -- all zeros = dont display and return early
         if #final_values == 0 then
             return nil
         end
         end
          
          
Line 249: Line 198:
                 options.color = 'value'
                 options.color = 'value'
             end
             end
            if options.truncate then
             parts[#parts+1] = m_util.html.format_value(tpl_args, value, options)
                options.inline_class = (options.inline_class or '') .. ' u-truncate-line'
            end
           
             parts[#parts+1] = m_util.html.format_value(tpl_args, frame, value, options)
         end
         end
         if args.sep then
         if args.sep then
Line 279: Line 224:
end
end


function core.factory.damage_html(args)
function core.add_stat(tpl_args, stat_id, value, options)
     return function(tpl_args, frame)
    options = options or {}
         local keys = {
    local mod = options.mod
             min = args.key .. '_damage_min',
    local tbl = options.tbl or '_stats'
             max = args.key .. '_damage_max',
    tpl_args[tbl] = tpl_args[tbl] or {}
         }
    tpl_args[tbl][stat_id] = tpl_args[tbl][stat_id] or {
         local value = {}
        min = 0,
         for ktype, key in pairs(keys) do
        max = 0,
             value[ktype] = core.factory.infobox_line{
        avg = 0,
                 parts = {
    }
                     {
    local stat = {
        mod = mod,
    }
    if type(value) == 'table' then
        stat.min = value.min
        stat.max = value.max
        stat.avg = value.avg or (stat.min + stat.max) / 2
    else
        stat.min = value
        stat.max = value
        stat.avg = value
    end
    table.insert(tpl_args[tbl][stat_id], stat)
    -- Totals
    tpl_args[tbl][stat_id].min = tpl_args[tbl][stat_id].min + stat.min
    tpl_args[tbl][stat_id].max = tpl_args[tbl][stat_id].max + stat.max
    tpl_args[tbl][stat_id].avg = tpl_args[tbl][stat_id].avg + stat.avg
end
 
--
-- Functions for processing tpl_args
--
h.proc = {}
h.proc.factory = {}
 
function h.proc.factory.value(args)
    args = args or {}
    return function (tpl_args, value)
        if value == nil then
            return nil
        end
        if args.cast then
            value = args.cast(value)
        end
        if args.validate then
            value = args.validate(value)
        end
        return value
    end
end
 
function h.proc.factory.list(args)
    args = args or {}
    return function (tpl_args, value)
        return m_util.cast.table(value, {
            pattern = args.pattern,
            callback = args.callback,
        })
    end
end
 
function h.proc.factory.damage_html(args)
     return function (tpl_args, value)
         local keys = {
             min = args.type .. '_damage_min',
             max = args.type .. '_damage_max',
         }
         local range = {}
         for ktype, key in pairs(keys) do
             range[ktype] = core.factory.infobox_line{
                 parts = {
                     {
                         key = key,
                         key = key,
                         color = false,
                         color = false,
Line 295: Line 301:
                     }
                     }
                 }
                 }
             }(tpl_args, frame)
             }(tpl_args)
         end
         end
         if value.min and value.max then
         if range.min and range.max then
             local color = args.key or false
             local color = args.type or false
             local range_fmt
             local range_fmt
             if tpl_args[keys.min .. '_range_minimum'] ~= tpl_args[keys.min .. '_range_maximum'] or tpl_args[keys.max .. '_range_minimum'] ~= tpl_args[keys.max .. '_range_maximum'] then
             if tpl_args[keys.min .. '_range_minimum'] ~= tpl_args[keys.min .. '_range_maximum'] or tpl_args[keys.max .. '_range_minimum'] ~= tpl_args[keys.max .. '_range_maximum'] then
                 -- Variable damage range, based on modifier rolls
                 -- Variable damage range, based on modifier rolls
                 if args.key == 'physical' then
                 if args.type == 'physical' then
                     color = 'mod'
                     color = 'mod'
                 end
                 end
Line 308: Line 314:
             else
             else
                 -- Standard damage range
                 -- Standard damage range
                 if args.key == 'physical' then
                 if args.type == 'physical' then
                     color = 'value'
                     color = 'value'
                 end
                 end
                 range_fmt = i18n.fmt.standard_damage_range
                 range_fmt = i18n.fmt.standard_damage_range
             end
             end
             value = string.format(range_fmt, value.min, value.max)
             value = string.format(range_fmt, range.min, range.max)
             if color then
             if color then
                 value = m_util.html.poe_color(color, value)
                 value = m_util.html.poe_color(color, value)
             end
             end
            tpl_args[args.key .. '_damage_html'] = value
         end
         end
        return value
     end
     end
end
end


function core.stats_update(tpl_args, id, value, modid, key)
function h.proc.factory.stat_text(args)
    if tpl_args[key][id] == nil then
    return function (tpl_args, value)
         tpl_args[key][id] = {
         local type_map = {
            references = {modid},
             implicit = 'is_implicit',
             min = value.min,
             explicit = 'is_explicit',
             max = value.max,
            avg = value.avg,
         }
         }
    else
         if type_map[args.type] == nil then
         if modid ~= nil then
             return nil
             table.insert(tpl_args[key][id].references, modid)
         end
         end
         tpl_args[key][id].min = tpl_args[key][id].min + value.min
         local lines = {}
        tpl_args[key][id].max = tpl_args[key][id].max + value.max
        local random_mods = {}
         tpl_args[key][id].avg = tpl_args[key][id].avg + value.avg
        local skip = cfg.class_specifics[tpl_args.class_id] and cfg.class_specifics[tpl_args.class_id].skip_stat_lines or nil
     end
        for _, mod_data in ipairs(tpl_args._mods) do
end
            if mod_data[type_map[args.type]] then
 
                if mod_data.is_random == true then
--
                    random_mods[mod_data.stat_text] = random_mods[mod_data.stat_text] or {}
-- argument mapping
                    table.insert(random_mods[mod_data.stat_text], mod_data)
                else
                    if mod_data.stat_text ~= nil then
                        table.insert(lines, mod_data.stat_text)
                    else
                        local text = mod_data.result['mods.stat_text']
                        if text and text ~= '' then
                            for _, line in ipairs(m_util.string.split(text, '<br>')) do
                                local skipped = false
                                if skip then
                                    for _, pattern in ipairs(skip) do
                                        if string.match(line, pattern) then
                                            skipped = true
                                            break
                                        end
                                    end
                                end
                                if not skipped then
                                    table.insert(lines, line)
                                end
                            end
                        end
                    end
                end
            end
        end
         for stat_text, mod_data_list in pairs(random_mods) do
            local text = {}
            for _, mod_data in ipairs(mod_data_list) do
                table.insert(text, mod_data.result['mods.stat_text'])
            end
            local tbl = mw.html.create('table')
            tbl
                :attr('class', 'random-modifier-stats mw-collapsed')
                :attr('style', 'text-align: left')
                :tag('tr')
                    :tag('th')
                        :attr('class', 'mw-customtoggle-31')
                        :wikitext(stat_text)
                        :done()
                    :done()
                :tag('tr')
                    :attr('class', 'mw-collapsible mw-collapsed')
                    :attr('id', 'mw-customcollapsible-31')
                    :tag('td')
                        :wikitext(table.concat(text, '<hr style="width: 20%">'))
                        :done()
                    :done()
            table.insert(lines, tostring(tbl))
        end
        return #lines > 0 and table.concat(lines, '<br>') or nil
     end
end
 
h.proc.text = h.proc.factory.value{cast = m_util.cast.text}
h.proc.boolean = h.proc.factory.value{cast = m_util.cast.boolean}
h.proc.number = h.proc.factory.value{cast = m_util.cast.number}
 
h.proc.percentage = h.proc.factory.value{
    cast = m_util.cast.number,
    validate = m_util.validate.factory.number_in_range{
        min = 0,
        max = 100,
    },
}
 
h.proc.size = h.proc.factory.value{
    cast = m_util.cast.number,
    validate = m_util.validate.factory.number_in_range{
        min = 1,
        max = 4,
    },
}
 
h.proc.character = h.proc.factory.value{
    validate = m_util.validate.factory.string_length{
        min = 1,
        max = 1,
    },
}
 
h.proc.list = h.proc.factory.list()
 
--
--
-- format:
-- Argument mapping
-- tpl_args key = {
--
--  no_copy = true or nil          -- When loading an base item, dont copy this key  
-- [<tpl_args key>] = {
--  property = 'prop',              -- Property associated with this key
--  inherit  boolean  Whether the item will inherit this key from its base item. Default: true
--  property_func = function or nil -- Function to unpack the property into a native lua value.
--  field  string  Cargo field name
--                                      If not specified, func is used.
--  type  string Cargo field type
--                                      If neither is specified, value is copied as string
--  func function Function to unpack the argument into a native lua value and validate it
--  func = function or nil          -- Function to unpack the argument into a native lua value and validate it.
--   default  varies  Default value if parameter is not set
--                                     If not specified, value will not be set.
--  deprecated  boolean  Set to true for deprecated parameters
--  default = object                -- Default value if the parameter is nil
-- }
-- }
core.map = {
core.map = {
     -- special params
     -- special params
     html = {
     html = {
         no_copy = true,
         inherit = false,
         field = 'html',
         field = 'html',
         type = 'Text',
         type = 'Text',
         func = nil,
         func = nil,
     },
     },
     html_extra = {
     infobox_html = {
         no_copy = true,
         inherit = false,
         field = 'html_extra',
         field = 'infobox_html',
        type = 'Text',
        func = nil,
    },
    metabox_html = {
        inherit = false,
        field = 'metabox_html',
         type = 'Text',
         type = 'Text',
         func = nil,
         func = nil,
     },
     },
     implicit_stat_text = {
     implicit_stat_text = {
        inherit = false,
         field = 'implicit_stat_text',
         field = 'implicit_stat_text',
         type = 'Text',
         type = 'Text',
         func = function(tpl_args, frame)
         func = h.proc.factory.stat_text{type='implicit'},
            tpl_args.implicit_stat_text = h.process_mod_stats(tpl_args, {is_implicit=true})
        end,
     },
     },
     explicit_stat_text = {
     explicit_stat_text = {
        inherit = false,
         field = 'explicit_stat_text',
         field = 'explicit_stat_text',
         type = 'Text',
         type = 'Text',
         func = function(tpl_args, frame)
        func = h.proc.factory.stat_text{type='explicit'},
             tpl_args.explicit_stat_text = h.process_mod_stats(tpl_args, {is_implicit=false})
    },
           
    stat_text = {
            if tpl_args.is_talisman or tpl_args.is_corrupted then
        inherit = false,
                 if tpl_args.explicit_stat_text == nil or tpl_args.explicit_stat_text == '' then
        field = 'stat_text',
                     tpl_args.explicit_stat_text = i18n.tooltips.corrupted
        type = 'Text',
                 else
         func = function (tpl_args, value)
                     tpl_args.explicit_stat_text = (tpl_args.explicit_stat_text or '') .. '<br>' .. i18n.tooltips.corrupted
             if tpl_args.implicit_stat_text or tpl_args.explicit_stat_text then
                local stats = {}
                table.insert(stats, tpl_args.implicit_stat_text) -- No-op if value is nil
                table.insert(stats, tpl_args.explicit_stat_text)
                 if tpl_args.is_corrupted then
                     table.insert(stats, m_util.html.poe_color('corrupted', i18n.tooltips.corrupted))
                 elseif tpl_args.is_mirrored then
                     table.insert(stats, i18n.tooltips.mirrored)
                elseif tpl_args.is_unmodifiable then
                    table.insert(stats, i18n.tooltips.unmodifiable)
                 end
                 end
                local sep = string.format('<span class="item-stat-separator -%s"></span>', tpl_args.frame_type)
                value = table.concat(stats, sep)
             end
             end
            return value
         end,
         end,
     },
     },
     stat_text = {
     class_id = {
         field = 'stat_text',
        inherit = false,
         type = 'Text',
         field = 'class_id',
         func = function(tpl_args, frame)
         type = 'String',
             local sep = ''
         func = function (tpl_args, value)
            if tpl_args.implicit_stat_text and tpl_args.explicit_stat_text then
             if value == nil then
                 sep = string.format('<span class="item-stat-separator -%s"></span>', tpl_args.frame_type)
                 error(string.format(i18n.errors.generic_required_parameter, 'class_id'))
             end
             end
             local text = (tpl_args.implicit_stat_text or '') .. sep .. (tpl_args.explicit_stat_text or '')
             if not m_util.table.has_key(m_game.constants.item.classes, value) or m_game.constants.item.classes[value].disabled then
           
                error(string.format(i18n.errors.invalid_class_id, tostring(value)))
            if string.len(text) > 0 then
                tpl_args.stat_text = text
             end
             end
            return value
         end,
         end,
     },
     },
     class = {
     class = {
         no_copy = true,
         inherit = false,
         field = 'class',
         field = 'class',
         type = 'String',
         type = 'String',
         func = function (tpl_args, frame)
         func = function (tpl_args, value)
             tpl_args.class = m_game.constants.item.classes[tpl_args.class_id]['long_upper']
             local class = m_game.constants.item.classes[tpl_args.class_id].long_upper
             -- Avoids errors with empty item class names later on
             -- Avoids errors with empty item class names later on
             if tpl_args.class == '' then
             if class == '' then
                 tpl_args.class = nil
                 class = nil
             end
             end
            return class
         end,
         end,
        deprecated = true,
     },
     },
     -- processed in build_item_classes
     -- generic
     class_id = {
     is_in_game = {
         no_copy = true,
         inherit = false,
         field = 'class_id',
         field = 'is_in_game',
         type = 'String',
         type = 'Boolean',
         func = function (tpl_args, frame)
         func = h.proc.boolean,
            if m_game.constants.item.classes[tpl_args.class_id] == nil then
         default = true,
                error(string.format(i18n.errors.invalid_class_id, tostring(tpl_args.class_id)))
            end
         end
     },
     },
    -- generic
     rarity_id = {
     rarity_id = {
         no_copy = true,
         inherit = false,
         field = 'rarity_id',
         field = 'rarity_id',
         type = 'String',
         type = 'String',
         func = function (tpl_args, frame)
         func = h.proc.factory.value{
             if m_game.constants.rarities[tpl_args.rarity_id] == nil then
             validate = m_util.validate.factory.in_table_keys{
                 error(string.format(i18n.errors.invalid_rarity_id, tostring(tpl_args.rarity_id)))
                tbl = m_game.constants.rarities,
             end
                 errmsg = i18n.errors.invalid_rarity_id,
         end
             },
         },
     },
     },
     rarity = {
     rarity = {
         no_copy = true,
         inherit = false,
         field = 'rarity',
         field = 'rarity',
         type = 'String',
         type = 'String',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             tpl_args.rarity = m_game.constants.rarities[tpl_args.rarity_id]['long_upper']
             return m_game.constants.rarities[tpl_args.rarity_id].long_upper
         end
         end,
        deprecated = true,
     },
     },
     name = {
     name = {
         no_copy = true,
         inherit = false,
         field = 'name',
         field = 'name',
         type = 'String',
         type = 'String',
Line 456: Line 557:
         field = 'size_x',
         field = 'size_x',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('size_x'),
         func = h.proc.size,
        default = 1,
     },
     },
     size_y = {
     size_y = {
         field = 'size_y',
         field = 'size_y',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('size_y'),
         func = h.proc.size,
        default = 1,
     },
     },
     drop_rarities_ids = {
     drop_rarities_ids = {
         no_copy = true,
         inherit = false,
         field = 'drop_rarity_ids',
         field = 'drop_rarity_ids',
         type = 'List (,) of Text',
         type = 'List (,) of String',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
            tpl_args.drop_rarities_ids = nil
             -- Drop rarities only matter for base items
            if true then return end
             if tpl_args._flags.is_derived then
             -- Drop rarities only matter for base items.
                 return nil -- Use default
             if tpl_args.rarity_id ~= 'normal' then
                return
            end
           
            if tpl_args.drop_rarities_ids == nil then
                tpl_args.drop_rarities_ids = {}
                 return
            end
               
            tpl_args.drop_rarities_ids = m_util.string.split(tpl_args.drop_rarities_ids, ',%s*')
            for _, rarity_id in ipairs(tpl_args.drop_rarities_ids) do
                if m_game.constants.rarities[rarity_id] == nil then
                    error(string.format(i18n.errors.invalid_rarity_id, tostring(rarity_id)))
                end
             end
             end
            return m_util.cast.table(value, {
                callback = m_util.validate.factory.in_table_keys{
                    tbl = m_game.constants.rarities,
                    errmsg = i18n.errors.invalid_rarity_id,
                    errlvl = 4,
                },
            })
         end,
         end,
    },
         default = {},
    drop_rarities = {
        no_copy = true,
        field = nil,
         type = 'List (,) of Text',
        func = function(tpl_args, frame)
            tpl_args.drop_rarities = nil
            if true then return end
            -- Drop rarities only matter for base items.
            if tpl_args.rarity_id ~= 'normal' then
                return
            end
           
            local rarities = {}
            for _, rarity_id in ipairs(tpl_args.drop_rarities_ids) do
                rarities[#rarities+1] = m_game.constants.rarities[rarity_id].full
            end
            tpl_args.drop_rarities = rarities
        end,
     },
     },
     drop_enabled = {
     drop_enabled = {
         no_copy = true,
         inherit = false,
         field = 'drop_enabled',
         field = 'drop_enabled',
         type = 'Boolean',
         type = 'Boolean',
         func = m_util.cast.factory.boolean('drop_enabled'),
         func = function (tpl_args, value)
         default = true,
            if value == nil then
                return nil -- Use default
            end
            return tpl_args.is_in_game and m_util.cast.boolean(value)
        end,
         default = function (tpl_args)
            return tpl_args.is_in_game
        end,
     },
     },
     drop_level = {
     drop_level = {
         no_copy = true,
         inherit = false,
         field = 'drop_level',
         field = 'drop_level',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('drop_level'),
         func = h.proc.number,
     },
     },
     drop_level_maximum = {
     drop_level_maximum = {
         no_copy = true,
         inherit = false,
         field = 'drop_level_maximum',
         field = 'drop_level_maximum',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('drop_level_maximum'),
         func = h.proc.number,
     },
     },
     drop_leagues = {
     acquisition_tags = {
         no_copy = true,
         inherit = false,
         field = 'drop_leagues',
         field = 'acquisition_tags',
         type = 'List (,) of String',
         type = 'List (,) of String',
         func = m_util.cast.factory.assoc_table('drop_leagues', {tbl=m_game.constants.leagues, errmsg=i18n.errors.invalid_league}),
         func = h.proc.factory.list{
            callback = m_util.validate.factory.in_table_keys{
                tbl = cfg.acquisition_tags,
                errmsg = i18n.errors.invalid_acquisition_tag,
                errlvl = 4,
            },
        },
        default = {},
     },
     },
     drop_areas = {
     drop_areas = {
         no_copy = true,
         inherit = false,
         field = 'drop_areas',
         field = 'drop_areas',
         type = 'List (,) of String',
         type = 'List (,) of String',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             if tpl_args.drop_areas ~= nil then
             value = m_util.cast.table(value)
                tpl_args.drop_areas = m_util.string.split(tpl_args.drop_areas, ',%s*')
            if type(value) == 'table' and #value > 0 then
                 tpl_args.drop_areas_data = m_cargo.array_query{
                 tpl_args._drop_areas_data = m_cargo.array_query{
                     tables={'areas'},
                     tables = {'areas'},
                     fields={'areas._pageName', 'areas.id', 'areas.name', 'areas.main_page'},
                     fields = {
                     id_field='areas.id',
                        'areas._pageName=_pageName',
                     id_array=tpl_args.drop_areas,
                        'areas.id=id',
                     query={limit=5000},
                        'areas.name=name',
                        'areas.main_page=main_page',
                        'areas.is_legacy_map_area=is_legacy_map_area'
                    },
                     id_field = 'areas.id',
                     id_array = value,
                     query = {limit=5000},
                 }
                 }
                if tpl_args._drop_areas_data then
                    tpl_args._legacy_drop_areas = tpl_args._legacy_drop_areas or {}
                    for _, v in ipairs(tpl_args._drop_areas_data) do
                        if m_util.cast.boolean(v.is_legacy_map_area) then
                            tpl_args._flags.has_legacy_drop_areas = true
                            table.insert(tpl_args._legacy_drop_areas, v.id)
                        end
                    end
                end
             end
             end
              
             return value
            -- find areas based on item tags for atlas bases
            local query_data
            for _, tag in ipairs(tpl_args.tags or {}) do
                query_data = nil
                if string.match(tag, '[%w_]*atlas[%w_]*') ~= nil and tag ~= 'atlas_base_type' then
                    query_data = m_cargo.query(
                        {'atlas_maps', 'maps', 'areas', 'atlas_base_item_types'},
                        {'areas._pageName', 'areas.id', 'areas.name', 'areas.main_page'},
                        {
                            join='atlas_maps._pageID=maps._pageID, maps.area_id=areas.id, atlas_maps.region_id=atlas_base_item_types.region_id',
                            where=string.format([[
                                    atlas_base_item_types.tag = "%s"
                                    AND atlas_base_item_types.weight > 0
                                    AND (
                                        atlas_maps.map_tier0 >= tier_min AND atlas_maps.map_tier0 <= atlas_base_item_types.tier_max
                                        OR atlas_maps.map_tier1 >= tier_min AND atlas_maps.map_tier1 <= atlas_base_item_types.tier_max
                                        OR atlas_maps.map_tier2 >= tier_min AND atlas_maps.map_tier2 <= atlas_base_item_types.tier_max
                                        OR atlas_maps.map_tier3 >= tier_min AND atlas_maps.map_tier3 <= atlas_base_item_types.tier_max
                                        OR atlas_maps.map_tier4 >= tier_min AND atlas_maps.map_tier4 <= atlas_base_item_types.tier_max
                                    )]],
                                tag),
                            groupBy='areas.id',
                        }
                    )
                end
               
                if query_data ~= nil then
                    -- in case no manual drop areas have been set
                    if tpl_args.drop_areas == nil then
                        tpl_args.drop_areas = {}
                        tpl_args.drop_areas_data = {}
                    end
                    local drop_areas_assoc = {}
                    for _, id in ipairs(tpl_args.drop_areas) do
                        drop_areas_assoc[id] = true
                    end
                   
                    local duplicates = {}
                   
                    for _, row in ipairs(query_data) do
                        if drop_areas_assoc[row['areas.id']] == nil then
                            tpl_args.drop_areas[#tpl_args.drop_areas+1] = row['areas.id']
                            tpl_args.drop_areas_data[#tpl_args.drop_areas_data+1] = row
                        else
                            duplicates[#duplicates+1] = row['areas.id']
                        end
                    end
                   
                    if #duplicates > 0 then
                        tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.duplicate_area_id_from_query, table.concat(duplicates, ', '))
                        tpl_args._flags.duplicate_query_area_ids = true
                    end
                end
            end
         end,
         end,
        default = {},
     },
     },
     drop_monsters = {
     drop_monsters = {
         no_copy = true,
         inherit = false,
         field = 'drop_monsters',
         field = 'drop_monsters',
         type = 'List (,) of Text',
         type = 'List (,) of Text',
         func = function (tpl_args, frame)
         func = h.proc.list,
            if tpl_args.drop_monsters ~= nil then
         default = {},
                tpl_args.drop_monsters = m_util.string.split(tpl_args.drop_monsters, ',%s*')
            end
         end,
     },
     },
     drop_text = {
     drop_text = {
         no_copy = true,
         inherit = false,
         field = 'drop_text',
         field = 'drop_text',
         type = 'Text',
         type = 'Text',
         func = h.factory.cast_text('drop_text'),
         func = h.proc.text,
     },
     },
     required_level = {
     required_level = {
         field = 'required_level_base',
         field = 'required_level_base',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('required_level'),
         func = h.proc.number,
         default = 1,
         default = 1,
     },
     },
Line 629: Line 680:
         field = 'required_level',
         field = 'required_level',
         type = 'Integer',
         type = 'Integer',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             tpl_args.required_level_final = tpl_args.required_level
             value = tpl_args.required_level
            if value < cfg.base_item_required_level_threshold and cfg.base_item_required_level_threshold_classes[tpl_args.class_id] then
                value = 1
            end
            return value
         end,
         end,
         default = 1,
         default = 1,
Line 637: Line 692:
         field = 'required_dexterity',
         field = 'required_dexterity',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('required_dexterity'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 643: Line 698:
         field = 'required_strength',
         field = 'required_strength',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('required_strength'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 649: Line 704:
         field = 'required_intelligence',
         field = 'required_intelligence',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('required_intelligence'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
     inventory_icon = {
     inventory_icon = {
         no_copy = true,
         inherit = false,
         field = 'inventory_icon',
         field = 'inventory_icon',
         type = 'String',
         type = 'String',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             if not tpl_args.inventory_icon then
             if not value then
                 -- Certain types of items have default inventory icons
                 -- Certain types of items have default inventory icons
                 if i18n.default_inventory_icons[tpl_args.class_id] then
                 if i18n.default_inventory_icons[tpl_args.class_id] then
                     tpl_args.inventory_icon = i18n.default_inventory_icons[tpl_args.class_id]
                     value = i18n.default_inventory_icons[tpl_args.class_id]
                 elseif tpl_args._flags.is_prophecy then
                 else
                    tpl_args.inventory_icon = i18n.default_inventory_icons['Prophecy']
                    for k, v in pairs(tpl_args._flags) do
                        value = v and i18n.default_inventory_icons[k]
                    end
                 end
                 end
             end
             end
             tpl_args.inventory_icon_id = tpl_args.inventory_icon or tpl_args.name
             tpl_args.inventory_icon_id = value or tpl_args.name
             tpl_args.inventory_icon = string.format(i18n.files.inventory_icon, tpl_args.inventory_icon_id)  
             return string.format(i18n.files.inventory_icon, tpl_args.inventory_icon_id)  
         end,
         end,
     },
     },
     -- note: this must be called after inventory_icon to work correctly as it depends on tpl_args.inventory_icon_id being set
     -- note: this must be called after inventory_icon to work correctly as it depends on tpl_args.inventory_icon_id being set
     alternate_art_inventory_icons = {
     alternate_art_inventory_icons = {
         no_copy = true,
         inherit = false,
         field = 'alternate_art_inventory_icons',
         field = 'alternate_art_inventory_icons',
         type = 'List (,) of String',
         type = 'List (,) of String',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             local icons = {}
             return m_util.cast.table(value, {
            if tpl_args.alternate_art_inventory_icons ~= nil then
                 callback = function (value)
                local names = m_util.string.split(tpl_args.alternate_art_inventory_icons, ',%s*')
                     return string.format(i18n.files.inventory_icon, string.format('%s %s', tpl_args.inventory_icon_id, tostring(value)))
                  
                 end,
                for _, name in ipairs(names) do
             })
                     icons[#icons+1] = string.format(i18n.files.inventory_icon, string.format('%s %s', tpl_args.inventory_icon_id, name))
                 end
             end
            tpl_args.alternate_art_inventory_icons = icons
         end,
         end,
         default = function (tpl_args, frame) return {} end,
         default = {},
     },
     },
     cannot_be_traded_or_modified = {
     cannot_be_traded_or_modified = {
         no_copy = true,
         inherit = false,
         field = 'cannot_be_traded_or_modified',
         field = 'cannot_be_traded_or_modified',
         type = 'Boolean',
         type = 'Boolean',
         func = m_util.cast.factory.boolean('cannot_be_traded_or_modified'),
         func = h.proc.boolean,
         default = false,
         default = false,
     },
     },
     help_text = {
     help_text = {
        debug_ignore_nil = true,
         field = 'help_text',
         field = 'help_text',
         type = 'Text',
         type = 'Text',
         func = h.factory.cast_text('help_text'),
         func = h.proc.text,
    },
    is_account_bound = {
        inherit = false,
        field = 'is_account_bound',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
     },
     },
     flavour_text = {
     flavour_text = {
         no_copy = true,
         inherit = false,
         field = 'flavour_text',
         field = 'flavour_text',
         type = 'Text',
         type = 'Text',
         func = h.factory.cast_text('flavour_text'),
         func = h.proc.factory.value{
            cast = function (value)
                value = m_util.cast.text(value)
                -- Parse harbinger glyphs
                value = string.gsub(value, '<<([Hh][Bb][Gg]%w+)>>', '<span class="glyph %1"></span>')
                return value
            end,
        },
     },
     },
     flavour_text_id = {
     flavour_text_id = {
         no_copy = true,
         inherit = false,
         field = 'flavour_text_id',
         field = 'flavour_text_id',
         type = 'String',
         type = 'String',
Line 715: Line 781:
         field = 'tags',
         field = 'tags',
         type = 'List (,) of String',
         type = 'List (,) of String',
         func = m_util.cast.factory.assoc_table('tags', {
         func = h.proc.factory.list{
            tbl = m_game.constants.tags,
            callback = m_util.validate.factory.in_table_keys{
            errmsg = i18n.errors.invalid_tag,
                tbl = m_game.constants.tags,
         }),
                errmsg = i18n.errors.invalid_tag,
                errlvl = 4,
            },
        },
         default = {},
     },
     },
     metadata_id = {
     metadata_id = {
         no_copy = true,
         inherit = false,
         field = 'metadata_id',
         field = 'metadata_id',
         type = 'String',
         type = 'String',
        --type = 'String(unique; size=200)',
         func = function (tpl_args, value)
         func = function(tpl_args, frame)
             if value == nil then
             if tpl_args.metadata_id == nil then
                 return nil
                 return
             end
             end
             local results = m_cargo.query(
             -- Unless we're in testing mode, validate that metadata_id is unique
                {'items'},
            if not tpl_args.test then
                {'items._pageName'},
                local results = m_cargo.query(
                {
                    {'items'},
                    where=string.format('items.metadata_id="%s" AND items._pageName != "%s"', tpl_args.metadata_id, mw.title.getCurrentTitle().fullText)
                    {'items._pageName'},
                }
                    {
            )
                        where = string.format(
            if #results > 0 and tpl_args.debug_id == nil and not tpl_args.debug then
                            'items.metadata_id = "%s" AND items._pageName != "%s"',
                error(string.format(i18n.errors.duplicate_metadata, tpl_args.metadata_id, results[1]['items._pageName']))
                            value,
                            m_cargo.addslashes(mw.title.getCurrentTitle().prefixedText)
                        )
                    }
                )
                if #results > 0 then
                    error(string.format(i18n.errors.duplicate_metadata, value, results[1]['items._pageName']))
                end
             end
             end
            return value
         end,
         end,
     },
     },
     influences = {
     influences = {
         no_copy = true,
         inherit = false,
         field = 'influences',
         field = 'influences',
         type = 'List (,) of String',
         type = 'List (,) of String',
         func = m_util.cast.factory.assoc_table('influences', {
         func = h.proc.factory.list{
            tbl = m_game.constants.influences,
            callback = m_util.validate.factory.in_table_keys{
            errmsg = i18n.errors.invalid_influence,
                tbl = m_game.constants.influences,
         }),
                errmsg = i18n.errors.invalid_influence,
                errlvl = 4,
            },
        },
         default = {},
     },
     },
     is_fractured = {
     is_fractured = {
         no_copy = true,
         inherit = false,
         field = 'is_fractured',
         field = 'is_fractured',
         type = 'Boolean',
         type = 'Boolean',
         func = m_util.cast.factory.boolean('is_fractured'),
         func = h.proc.boolean,
         default = false,
         default = false,
     },
     },
     is_synthesised = {
     is_synthesised = {
         no_copy = true,
         inherit = false,
         field = 'is_synthesised',
         field = 'is_synthesised',
         type = 'Boolean',
         type = 'Boolean',
         func = m_util.cast.factory.boolean('is_synthesised'),
         func = h.proc.boolean,
        default = false,
    },
    is_searing_exarch_item = {
        inherit = false,
        field = 'is_searing_exarch_item',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    is_eater_of_worlds_item = {
        inherit = false,
        field = 'is_eater_of_worlds_item',
        type = 'Boolean',
        func = h.proc.boolean,
         default = false,
         default = false,
     },
     },
     is_veiled = {
     is_veiled = {
         no_copy = true,
         inherit = false,
         field = 'is_veiled',
         field = 'is_veiled',
         type = 'Boolean',
         type = 'Boolean',
         func = m_util.cast.factory.boolean('is_veiled'),
         func = h.proc.boolean,
         default = false,
         default = false,
     },
     },
     is_replica = {
     is_replica = {
         no_copy = true,
         inherit = false,
         field = 'is_replica',
         field = 'is_replica',
         type = 'Boolean',
         type = 'Boolean',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             m_util.cast.factory.boolean('is_replica')(tpl_args, frame)
             value = m_util.cast.boolean(value)
             if tpl_args.is_replica == true and tpl_args.rarity_id ~= 'unique' then
             if value == true and tpl_args.rarity_id ~= 'unique' then
                 error(string.format(i18n.errors.non_unique_flag, 'is_replica'))
                 error(string.format(i18n.errors.non_unique_flag, 'is_replica'))
             end
             end
            return value
         end,
         end,
         default = false,
         default = false,
     },
     },
     is_corrupted = {
     is_corrupted = {
         no_copy = true,
         inherit = false,
         field = 'is_corrupted',
         field = 'is_corrupted',
         type = 'Boolean',
         type = 'Boolean',
         func = m_util.cast.factory.boolean('is_corrupted'),
         func = h.proc.boolean,
         default = false,
         default = false,
     },
     },
     is_relic = {
     is_mirrored = {
         no_copy = true,
         inherit = false,
         field = 'is_relic',
        field = nil,
        type = nil,
        func = h.proc.boolean,
        default = false,
    },
    is_unmodifiable = {
        inherit = false,
         field = 'is_unmodifiable',
         type = 'Boolean',
         type = 'Boolean',
         func = function(tpl_args, frame)
         func = h.proc.boolean,
            m_util.cast.factory.boolean('is_relic')(tpl_args, frame)
            if tpl_args.is_relic == true and tpl_args.rarity_id ~= 'unique' then
                error(string.format(i18n.errors.non_unique_flag, 'is_relic'))
            end
        end,
         default = false,
         default = false,
     },
     },
     is_fated = {
     is_drop_restricted = {
         no_copy = true,
         inherit = false,
         field = 'is_fated',
         field = 'is_drop_restricted',
         type = 'Boolean',
         type = 'Boolean',
         func = function(tpl_args, frame)
         func = h.proc.boolean,
             m_util.cast.factory.boolean('is_fated')(tpl_args, frame)
        default = function (tpl_args)
             if tpl_args.is_fated == true and tpl_args.rarity_id ~= 'unique' then
             -- Divination cards have drop restrictions by design.
                 error(string.format(i18n.errors.non_unique_flag, 'is_fated'))
             if tpl_args.class_id == 'DivinationCard' then
                 return true
             end
             end
        end,
             for _, key in ipairs({'is_replica', '_drop_areas_data', 'drop_monsters'}) do
        default = false,
                -- arg must be truthy and NOT an empty table
    },
                 if tpl_args[key] and not (type(tpl_args[key]) == 'table' and #tpl_args[key] == 0) then
    is_prophecy = {
                     return true
        no_copy = true,
        field = nil,
        type = nil,
        func = function(tpl_args, frame)
            tpl_args._flags.is_prophecy = (tpl_args.metadata_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy' or tpl_args.base_item == cfg.prophecy_base_item or tpl_args.base_item_page == cfg.prophecy_base_item_page or tpl_args.base_item_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy')
        end
    },
    is_blight_item = {
        no_copy = true,
        field = nil,
        type = nil,
        func = function(tpl_args, frame)
            tpl_args._flags.is_blight_item = (tpl_args.blight_item_tier ~= nil)
        end
    },
    is_drop_restricted = {
        no_copy  = true,
        field = 'is_drop_restricted',
        type = 'Boolean',
        func = m_util.cast.factory.boolean('is_drop_restricted'),
        default = function(tpl_args, frame)
            -- Generally items that are obtained only from specific monsters can't be obtained via cards or other means unless specifically specified
             for _, var in ipairs({'is_talisman', 'is_fated', 'is_relic', 'drop_monsters'}) do
                 if tpl_args[var] then
                     return true
                 end
                 end
             end
             end
             for _, flag in ipairs({'is_prophecy', 'is_blight_item'}) do
             local flags = {
                'is_talisman',
                'is_essence',
                'is_blight_item',
                'is_fossil',
                'is_tattoo',
                'is_delirium_orb',
                'is_catalyst',
            }
            for _, flag in ipairs(flags) do
                 if tpl_args._flags[flag] then
                 if tpl_args._flags[flag] then
                     return true
                     return true
Line 851: Line 934:
     },
     },
     purchase_costs = {
     purchase_costs = {
         func = function(tpl_args, frame)
        field = nil,
        type = nil,
         func = function (tpl_args, value)
             local purchase_costs = {}
             local purchase_costs = {}
             for _, rarity_id in ipairs(m_game.constants.rarity_order) do
             for _, rarity_id in ipairs(m_game.constants.rarity_order) do
Line 867: Line 952:
                         rtbl[#rtbl+1] = values
                         rtbl[#rtbl+1] = values
                         i = i + 1
                         i = i + 1
                          
                         table.insert(tpl_args._store_data, {
                        tpl_args._subobjects[#tpl_args._subobjects+1] = {
                             _table = 'item_purchase_costs',
                             _table = 'item_purchase_costs',
                             amount = values.amount,
                             amount = values.amount,
                             name = values.name,
                             name = values.name,
                             rarity = values.rarity,
                             rarity = values.rarity,
                         }
                         })
                     else
                     else
                         i = -1
                         i = -1
                     end
                     end
                 end
                 end
               
                 purchase_costs[rarity_id] = rtbl
                 purchase_costs[rarity_id] = rtbl
             end
             end
              
             return purchase_costs
            tpl_args.purchase_costs = purchase_costs
         end,
         end,
         func_fetch = function(tpl_args, frame)
         func_fetch = function (tpl_args)
             if tpl_args.rarity_id ~= 'unique' then
             if tpl_args.rarity_id ~= 'unique' then
                 return
                 return
Line 906: Line 988:
                 local datavar = tpl_args.purchase_costs[string.lower(values.rarity)]
                 local datavar = tpl_args.purchase_costs[string.lower(values.rarity)]
                 datavar[#datavar+1] = values
                 datavar[#datavar+1] = values
                  
                 table.insert(tpl_args._store_data, {
                tpl_args._subobjects[#tpl_args._subobjects+1] = {
                     _table = 'item_purchase_costs',
                     _table = 'item_purchase_costs',
                     amount = values.amount,
                     amount = values.amount,
                     name = values.name,
                     name = values.name,
                     rarity = values.rarity,
                     rarity = values.rarity,
                 }
                 })
            end
        end,
    },
    is_sellable = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            if value == nil then
                return nil -- Use default
             end
             end
            return tpl_args.is_in_game and m_util.cast.boolean(value)
        end,
        default = function (tpl_args)
            return tpl_args.is_in_game
         end,
         end,
     },
     },
     sell_prices_override = {
     sell_prices_override = {
         no_copy = true,
         inherit = false,
         func = function(tpl_args, frame)
        field = nil,
        type = nil,
         func = function (tpl_args, value)
             -- these variables are also used by mods when setting automatic sell prices
             -- these variables are also used by mods when setting automatic sell prices
             tpl_args.sell_prices = {}
             tpl_args.sell_prices = {}
             tpl_args.sell_price_order = {}
             tpl_args.sell_price_order = {}
              
             if not tpl_args.is_sellable then
              
                return nil
             end
             local name
             local name
             local amount
             local amount
Line 935: Line 1,033:
                     tpl_args.sell_price_order[#tpl_args.sell_price_order+1] = name
                     tpl_args.sell_price_order[#tpl_args.sell_price_order+1] = name
                     tpl_args.sell_prices[name] = amount
                     tpl_args.sell_prices[name] = amount
                     tpl_args._subobjects[#tpl_args._subobjects+1] = {
                     table.insert(tpl_args._store_data, {
                         _table = 'item_sell_prices',
                         _table = 'item_sell_prices',
                         amount = amount,
                         amount = amount,
                         name = name,
                         name = name,
                     }
                     })
                 end
                 end
             until name == nil or amount == nil  
             until name == nil or amount == nil  
           
             -- if sell prices are set, the override is active
             -- if sell prices are set, the override is active
             for _, _ in pairs(tpl_args.sell_prices) do
             for _, _ in pairs(tpl_args.sell_prices) do
Line 948: Line 1,045:
                 break
                 break
             end
             end
            return value
         end,
         end,
     },
     },
Line 953: Line 1,051:
     -- specific section
     -- specific section
     --
     --
   
 
     -- Most item classes
     -- Most item classes
     quality = {
     quality = {
         no_copy = true,
         inherit = false,
         field = 'quality',
         field = 'quality',
         type = 'Integer',
         type = 'Integer',
         -- Can be set manually, but default to Q20 for unique weapons/body armours
         -- Must copy to stat for the stat adjustments to work properly
        -- Also must copy to stat for the stat adjustments to work properly
         func = function (tpl_args, value)
         func = function(tpl_args, frame)
             local value = tonumber(value)
             local quality = tonumber(tpl_args.quality)
             if value then
             --
                core.add_stat(tpl_args, 'quality', value)
            if quality == nil then
                 if tpl_args.class_id == 'UtilityFlask' then
                 if tpl_args.rarity_id ~= 'unique' then
                     core.add_stat(tpl_args, 'quality_flask_duration', value)
                     quality = 0
                 elseif tpl_args.class_id == 'Map' then
                 elseif cfg.class_groups.weapons.keys[tpl_args.class_id] or cfg.class_groups.armor.keys[tpl_args.class_id] then
                     -- quality is added to quantity for maps
                     quality = 20
                     core.add_stat(tpl_args, 'map_item_drop_quantity_+%', value)
                else
                     quality = 0
                 end
                 end
             end
             end
              
             return value
            tpl_args.quality = quality
           
            local stat = {
                min = quality,
                max = quality,
                avg = quality,
            }
           
            core.stats_update(tpl_args, 'quality', stat, nil, '_stats')
           
            if tpl_args.class_id == 'UtilityFlask' or tpl_args.class_id == 'UtilityFlaskCritical' then
                core.stats_update(tpl_args, 'quality_flask_duration', stat, nil, '_stats')
            -- quality is added to quantity for maps
            elseif tpl_args.class_id == 'Map' then
                core.stats_update(tpl_args, 'map_item_drop_quantity_+%', stat, nil, '_stats')
            end
         end,
         end,
        default = 0,
     },
     },
     -- amulets
     -- amulets
Line 996: Line 1,077:
         field = 'is_talisman',
         field = 'is_talisman',
         type = 'Boolean',
         type = 'Boolean',
         func = m_util.cast.factory.boolean('is_talisman'),
         func = function (tpl_args, value)
            value = m_util.cast.boolean(value)
            tpl_args._flags.is_talisman = value
            return value
        end,
         default = false,
         default = false,
     },
     },
   
     talisman_tier = {
     talisman_tier = {
         field = 'talisman_tier',
         field = 'talisman_tier',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('talisman_tier'),
         func = h.proc.number,
     },
     },
   
     -- flasks
     -- flasks
     charges_max = {
     charges_max = {
         field = 'charges_max',
         field = 'charges_max',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('charges_max'),
         func = h.proc.number,
     },
     },
     charges_per_use = {
     charges_per_use = {
         field = 'charges_per_use',
         field = 'charges_per_use',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('charges_per_use'),
         func = h.proc.number,
     },
     },
     flask_mana = {
     flask_mana = {
         field = 'mana',
         field = 'mana',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('flask_mana'),
         func = h.proc.number,
     },
     },
     flask_life = {
     flask_life = {
         field = 'life',
         field = 'life',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('flask_life'),
         func = h.proc.number,
     },
     },
     flask_duration = {
     flask_duration = {
         field = 'duration',
         field = 'duration',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('flask_duration'),
         func = h.proc.number,
     },
     },
     buff_id = {
     buff_id = {
Line 1,040: Line 1,123:
         field = 'buff_values',
         field = 'buff_values',
         type = 'List (,) of Integer',
         type = 'List (,) of Integer',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             local values = {}
             local values = {}
             local i = 0
             local i = 0
Line 1,052: Line 1,135:
             -- needed so the values copyied from unique item base isn't overriden
             -- needed so the values copyied from unique item base isn't overriden
             if #values >= 1 then
             if #values >= 1 then
                 tpl_args.buff_values = values
                 value = values
             end
             end
            return value
         end,
         end,
         func_copy = function(tpl_args, frame)
         func_copy = function (tpl_args, value)
             tpl_args.buff_values = m_util.string.split(tpl_args.buff_values, ',%s*')
             tpl_args.buff_values = m_util.string.split(value, ',%s*')
         end,
         end,
         default = function (tpl_args, frame) return {} end,
         default = {},
     },
     },
     buff_stat_text = {
     buff_stat_text = {
Line 1,068: Line 1,152:
         field = 'icon',
         field = 'icon',
         type = 'String',
         type = 'String',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             tpl_args.buff_icon = string.format(i18n.files.status_icon, tpl_args.name)  
             return string.format(i18n.files.status_icon, tpl_args.name)
         end,
         end,
     },
     },
Line 1,077: Line 1,161:
         field = 'critical_strike_chance',
         field = 'critical_strike_chance',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('critical_strike_chance'),
         func = h.proc.number,
     },
     },
     attack_speed = {
     attack_speed = {
         field = 'attack_speed',
         field = 'attack_speed',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('attack_speed'),
         func = h.proc.number,
     },
     },
     weapon_range = {
     weapon_range = {
         field = 'weapon_range',
         field = 'weapon_range',
         type = 'Integer',
         type = 'Float',
         func = m_util.cast.factory.number('weapon_range'),
         func = h.proc.number,
     },
     },
     physical_damage_min = {
     physical_damage_min = {
         field = 'physical_damage_min',
         field = 'physical_damage_min',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('physical_damage_min'),
         func = h.proc.number,
        default = 0,
     },
     },
     physical_damage_max = {
     physical_damage_max = {
         field = 'physical_damage_max',
         field = 'physical_damage_max',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('physical_damage_max'),
         func = h.proc.number,
        default = 0,
     },
     },
     fire_damage_min = {
     fire_damage_min = {
         field = 'fire_damage_min',
         field = 'fire_damage_min',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('fire_damage_min'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 1,108: Line 1,194:
         field = 'fire_damage_max',
         field = 'fire_damage_max',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('fire_damage_max'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 1,114: Line 1,200:
         field = 'cold_damage_min',
         field = 'cold_damage_min',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('cold_damage_min'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 1,120: Line 1,206:
         field = 'cold_damage_max',
         field = 'cold_damage_max',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('cold_damage_max'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 1,126: Line 1,212:
         field = 'lightning_damage_min',
         field = 'lightning_damage_min',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('lightning_damage_min'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 1,132: Line 1,218:
         field = 'lightning_damage_max',
         field = 'lightning_damage_max',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('lightning_damage_max'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 1,138: Line 1,224:
         field = 'chaos_damage_min',
         field = 'chaos_damage_min',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('chaos_damage_min'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 1,144: Line 1,230:
         field = 'chaos_damage_max',
         field = 'chaos_damage_max',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('chaos_damage_max'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
     -- armor-type stuff
     -- armor-type stuff
     armour = {
     armour = {
        inherit = false,
         field = 'armour',
         field = 'armour',
        type = nil,
        func = nil,
    },
    armour_min = {
        field = 'armour_min',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('armour'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
     energy_shield = {
     armour_max = {
         field = 'energy_shield',
         field = 'armour_max',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('energy_shield'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
     evasion = {
     evasion = {
        inherit = false,
         field = 'evasion',
         field = 'evasion',
         type = 'Integer',
        type = nil,
         func = m_util.cast.factory.number('evasion'),
        func = nil,
    },
    evasion_min = {
        field = 'evasion_min',
         type = 'Integer',
         func = h.proc.number,
        default = 0,
    },
    evasion_max = {
        field = 'evasion_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    energy_shield = {
        inherit = false,
        field = 'energy_shield',
        type = nil,
        func = nil,
    },
    energy_shield_min = {
        field = 'energy_shield_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    energy_shield_max = {
        field = 'energy_shield_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    ward = {
        inherit = false,
        field = 'ward',
        type = nil,
        func = nil,
    },
    ward_min = {
        field = 'ward_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    ward_max = {
        field = 'ward_max',
        type = 'Integer',
        func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 1,170: Line 1,310:
         field = 'movement_speed',
         field = 'movement_speed',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('movement_speed'),
         func = h.proc.number,
         default = 0,
         default = 0,
     },
     },
Line 1,177: Line 1,317:
         field = 'block',
         field = 'block',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('block'),
         func = h.proc.number,
     },
     },
     -- skill gem stuff
     -- skill gem stuff
Line 1,183: Line 1,323:
         field = 'gem_description',
         field = 'gem_description',
         type = 'Text',
         type = 'Text',
         func = h.factory.cast_text('gem_description'),
         func = h.proc.text,
     },
     },
     dexterity_percent = {
     dexterity_percent = {
         field = 'dexterity_percent',
         field = 'dexterity_percent',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.percentage('dexterity_percent'),
         func = h.proc.percentage,
     },
     },
     strength_percent = {
     strength_percent = {
         field = 'strength_percent',
         field = 'strength_percent',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.percentage('strength_percent'),
         func = h.proc.percentage,
     },
     },
     intelligence_percent = {
     intelligence_percent = {
         field = 'intelligence_percent',
         field = 'intelligence_percent',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.percentage('intelligence_percent'),
         func = h.proc.percentage,
     },
     },
     primary_attribute = {
     primary_attribute = {
         field = 'primary_attribute',
         field = 'primary_attribute',
         type = 'String',
         type = 'String',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             for _, attr in ipairs(m_game.constants.attribute_order) do
             for _, attr in ipairs(m_game.constants.attribute_order) do
                 local val = tpl_args[attr .. '_percent']  
                 local val = tpl_args[attr .. '_percent']  
                 if val and val >= 60 then
                 if val and val >= 60 then
                     tpl_args['primary_attribute'] = attr
                     return attr
                    return
                 end
                 end
             end
             end
             tpl_args['primary_attribute'] = 'none'
             return 'none'
         end,
         end,
     },
     },
Line 1,217: Line 1,356:
         field = 'gem_tags',
         field = 'gem_tags',
         type = 'List (,) of String',
         type = 'List (,) of String',
        -- TODO: default rework
         func = h.proc.factory.list{
         func = function(tpl_args, frame)
             callback = m_util.validate.factory.in_table_keys{
             if tpl_args.gem_tags then
                 tbl = m_game.constants.item.gem_tags_lookup,
                 tpl_args.gem_tags = m_util.string.split(tpl_args.gem_tags, ',%s*')
                errmsg = i18n.errors.invalid_gem_tag,
             end
                errlvl = 4,
         end,
             },
         default = function (tpl_args, frame) return {} end,
         },
         default = {},
    },
    is_vaal_skill_gem = {
        field = 'is_vaal_skill_gem',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    vaal_variant_id = {
        inherit = true,
        field = 'vaal_variant_id',
        type = 'String',
        func = nil,
     },
     },
     -- Support gems only
     -- Support gems only
     support_gem_letter = {
     support_gem_letter = {
         field = 'support_gem_letter',
         field = 'support_gem_letter',
         type = 'String(size=1)',
         type = 'String',
         func = nil,
         func = h.proc.character,
     },
     },
     support_gem_letter_html = {
     support_gem_letter_html = {
         field = 'support_gem_letter_html',
         field = 'support_gem_letter_html',
         type = 'Text',
         type = 'Text',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             if tpl_args.support_gem_letter == nil then
             if tpl_args.support_gem_letter == nil then
                 return
                 return nil
             end
             end
       
             for k, v in pairs(m_game.constants.attributes) do
            -- TODO replace this with a loop possibly
                 local key = string.format('%s_percent', v.long_lower)
            local css_map = {
                 if tpl_args[key] and tpl_args[key] > 50 then
                strength = 'red',
                     value = tostring(
                intelligence = 'blue',
                        mw.html.create('span')
                dexterity = 'green',
                            :attr('class', string.format('support-gem-id-%s', v.color))
            }
                            :wikitext(tpl_args.support_gem_letter)
            local id
                    )
             for k, v in pairs(css_map) do
                 k = string.format('%s_percent', k)
                 if tpl_args[k] and tpl_args[k] > 50 then
                     id = v
                     break
                     break
                 end
                 end
             end
             end
              
             return value
            if id ~= nil then
                local container = mw.html.create('span')
                container
                    :attr('class', string.format('support-gem-id-%s', id))
                    :wikitext(tpl_args.support_gem_letter)
                    :done()
                tpl_args.support_gem_letter_html = tostring(container)
            end
         end,
         end,
    },
    is_awakened_support_gem = {
        field = 'is_awakened_support_gem',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    awakened_variant_id = {
        field = 'awakened_variant_id',
        type = 'String',
        func = nil,
    },
    --
    -- Jewels
    --
    jewel_limit = {
        inherit = false,
        field = 'jewel_limit',
        type = 'String',
        func = nil,
     },
     },
     --
     --
Line 1,270: Line 1,430:
         field = 'tier',
         field = 'tier',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('map_tier'),
         func = h.proc.number,
     },
     },
     map_guild_character = {
     map_guild_character = {
         field = 'guild_character',
         field = 'guild_character',
         type = 'String(size=1)',
         type = 'String',
         func = nil,
         func = h.proc.character,
     },
     },
     map_area_id = {
     map_area_id = {
Line 1,285: Line 1,445:
         field = 'area_level',
         field = 'area_level',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('map_area_level'),
         func = h.proc.number,
     },
     },
     unique_map_guild_character = {
     unique_map_guild_character = {
         field = 'unique_guild_character',
         field = 'unique_guild_character',
         type = 'String(size=1)',
         type = 'String',
         func_copy = function(tpl_args, frame)
        func = h.proc.character,
             tpl_args.map_guild_character = tpl_args.unique_map_guild_character
         func_copy = function (tpl_args, value)
             tpl_args.map_guild_character = value
         end,
         end,
        func = nil,
     },
     },
     unique_map_area_id = {
     unique_map_area_id = {
Line 1,299: Line 1,459:
         type = 'String',
         type = 'String',
         func = nil, -- TODO: Validate against a query?
         func = nil, -- TODO: Validate against a query?
         func_copy = function(tpl_args, frame)
         func_copy = function (tpl_args, value)
             tpl_args.map_area_id = tpl_args.unique_map_area_id
             tpl_args.map_area_id = value
         end,
         end,
     },
     },
Line 1,306: Line 1,466:
         field = 'unique_area_level',
         field = 'unique_area_level',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('unique_map_area_level'),
         func = h.proc.number,
         func_copy = function(tpl_args, frame)
         func_copy = function (tpl_args, value)
             tpl_args.map_area_level = tpl_args.unique_map_area_level
             tpl_args.map_area_level = value
         end,
         end,
     },
     },
Line 1,314: Line 1,474:
         field = 'series',
         field = 'series',
         type = 'String',
         type = 'String',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             if tpl_args.rarity == 'normal' and tpl_args.map_series == nil then
             if tpl_args.rarity == 'normal' and value == nil then
                 error(string.format(i18n.errors.generic_required_parameter, 'map_series'))
                 error(string.format(i18n.errors.generic_required_parameter, 'map_series'))
             end
             end
            return value
         end,
         end,
     },
     },
     -- atlas info is only for the current map series
     -- atlas info is only for the current map series
     atlas_x = {
     atlas_x = {
        inherit = false,
         field = 'x',
         field = 'x',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_x'),
         func = h.proc.number,
     },
     },
     atlas_y = {
     atlas_y = {
        inherit = false,
         field = 'y',
         field = 'y',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_y'),
         func = h.proc.number,
     },
     },
     atlas_region_id = {
     atlas_region_id = {
        inherit = false,
         field = 'region_id',
         field = 'region_id',
         type = 'String',
         type = 'String',
Line 1,337: Line 1,501:
     },
     },
     atlas_region_minimum = {
     atlas_region_minimum = {
        inherit = false,
         field = 'region_minimum',
         field = 'region_minimum',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('atlas_region_minimum'),
         func = h.proc.number,
     },
     },
     atlas_x0 = {
     atlas_x0 = {
        inherit = false,
         field = 'x0',
         field = 'x0',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_x0'),
         func = h.proc.number,
     },
     },
     atlas_x1 = {
     atlas_x1 = {
        inherit = false,
         field = 'x1',
         field = 'x1',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_x1'),
         func = h.proc.number,
     },
     },
     atlas_x2 = {
     atlas_x2 = {
        inherit = false,
         field = 'x2',
         field = 'x2',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_x2'),
         func = h.proc.number,
     },
     },
     atlas_x3 = {
     atlas_x3 = {
        inherit = false,
         field = 'x3',
         field = 'x3',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_x3'),
         func = h.proc.number,
     },
     },
     atlas_x4 = {
     atlas_x4 = {
        inherit = false,
         field = 'x4',
         field = 'x4',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_x4'),
         func = h.proc.number,
     },
     },
     atlas_y0 = {
     atlas_y0 = {
        inherit = false,
         field = 'y0',
         field = 'y0',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_0'),
         func = h.proc.number,
     },
     },
     atlas_y1 = {
     atlas_y1 = {
        inherit = false,
         field = 'y1',
         field = 'y1',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_y1'),
         func = h.proc.number,
     },
     },
     atlas_y2 = {
     atlas_y2 = {
        inherit = false,
         field = 'y2',
         field = 'y2',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_y2'),
         func = h.proc.number,
     },
     },
     atlas_y3 = {
     atlas_y3 = {
        inherit = false,
         field = 'y3',
         field = 'y3',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_y3'),
         func = h.proc.number,
     },
     },
     atlas_y4 = {
     atlas_y4 = {
        inherit = false,
         field = 'y4',
         field = 'y4',
         type = 'Float',
         type = 'Float',
         func = m_util.cast.factory.number('atlas_y4'),
         func = h.proc.number,
     },
     },
     atlas_map_tier0 = {
     atlas_map_tier0 = {
         field = 'map_tier0',
         field = 'map_tier0',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('atlas_map_tier0'),
         func = h.proc.number,
     },
     },
     atlas_map_tier1 = {
     atlas_map_tier1 = {
         field = 'map_tier1',
         field = 'map_tier1',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('atlas_map_tier1'),
         func = h.proc.number,
     },
     },
     atlas_map_tier2 = {
     atlas_map_tier2 = {
         field = 'map_tier2',
         field = 'map_tier2',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('atlas_map_tier2'),
         func = h.proc.number,
     },
     },
     atlas_map_tier3 = {
     atlas_map_tier3 = {
         field = 'map_tier3',
         field = 'map_tier3',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('atlas_map_tier3'),
         func = h.proc.number,
     },
     },
     atlas_map_tier4 = {
     atlas_map_tier4 = {
         field = 'map_tier4',
         field = 'map_tier4',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('atlas_map_tier4'),
         func = h.proc.number,
     },
     },
     atlas_connections = {
     atlas_connections = {
        inherit = false,
         field = nil,
         field = nil,
         type = nil,
         type = nil,
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             tpl_args.atlas_connections = {}
             value = {}
           
             local cont = true
             local cont = true
             local i = 1
             local i = 1
Line 1,442: Line 1,617:
                     end
                     end
                      
                      
                     tpl_args.atlas_connections[data.map2] = data
                     value[data.map2] = data
                     table.insert(tpl_args._subobjects, data)
                     table.insert(tpl_args._store_data, data)
                 else
                 else
                     cont = false
                     cont = false
                     if i == 1 then
                     if i == 1 then
                         tpl_args.atlas_connections = nil
                         value = nil
                     end
                     end
                 end
                 end
               
                 i = i + 1
                 i = i + 1
             end
             end
            return value
         end,
         end,
        default = nil,
     },
     },
     --
     --
     -- Currency-like items
     -- Map fragments
    --
    is_scarab = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_scarab = m_util.table.contains(tpl_args.tags, 'scarab')
            return value
        end,
    },
    --
    -- Stackable items
     --
     --
     stack_size = {
     stack_size = {
        inherit = false,
         field = 'stack_size',
         field = 'stack_size',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('stack_size'),
         func = h.proc.number,
     },
     },
     stack_size_currency_tab = {
     stack_size_currency_tab = {
        inherit = false,
         field = 'stack_size_currency_tab',
         field = 'stack_size_currency_tab',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('stack_size_currency_tab'),
         func = h.proc.number,
     },
     },
     description = {
     description = {
        inherit = false,
         field = 'description',
         field = 'description',
         type = 'Text',
         type = 'Text',
         func = h.factory.cast_text('description'),
         func = h.proc.text,
     },
     },
    cosmetic_type = {
     -- Essences
        field = 'cosmetic_type',
        type = 'String',
        func = h.factory.cast_text('cosmetic_type'),
    },
     -- for essences
     is_essence = {
     is_essence = {
        inherit = false,
         field = nil,
         field = nil,
         func = m_util.cast.factory.boolean('is_essence'),
        type = nil,
         func = function (tpl_args, value)
            value = m_util.cast.boolean(value)
            tpl_args._flags.is_essence = value
            return value
        end,
         default = false,
         default = false,
     },
     },
     essence_level_restriction = {
     essence_level_restriction = {
        inherit = false,
         field = 'level_restriction',
         field = 'level_restriction',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('essence_level_restriction'),
         func = h.proc.number,
     },
     },
     essence_level = {
     essence_level = {
        inherit = false,
         field = 'level',
         field = 'level',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('essence_level'),
         func = h.proc.number,
     },
     },
     essence_type = {
     essence_type = {
        inherit = false,
         field = 'type',
         field = 'type',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('essence_type'),
         func = h.proc.number,
     },
     },
     essence_category = {
     essence_category = {
        inherit = false,
         field = 'category',
         field = 'category',
         type = 'String',
         type = 'String',
         func = nil,
         func = nil,
     },
     },
     -- blight crafting items (i.e. oils)
     -- Oils
    is_blight_item = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_blight_item = tpl_args.blight_item_tier ~= nil
            return value
        end,
    },
     blight_item_tier = {
     blight_item_tier = {
        inherit = false,
         field = 'tier',
         field = 'tier',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('blight_item_tier'),
         func = h.proc.number,
     },
     },
     -- harvest seeds
     -- Fossils
     seed_type_id = {
     is_fossil = {
         field = 'type_id',
        inherit = false,
         type = 'String',
         field = nil,
         type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_fossil = tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'CurrencyDelve', 1, true) ~= nil
            return value
        end,
     },
     },
     seed_type = {
     -- Tattoos
         field = 'type',
    is_tattoo = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_tattoo = tpl_args.tattoo_target ~= nil
            return value
        end,
    },
    tattoo_target = {
        inherit = false,
         field = 'target',
         type = 'String',
         type = 'String',
         func = function (tpl_args, frame)
         func = nil,
            if tpl_args.seed_type_id ~= 'none' or tpl_args.seed_type_id ~= nil then
                tpl_args.seed_type = m_game.seed_types[tpl_args.seed_type_id]
            end
        end
     },
     },
     seed_type_html = {
     tattoo_tribe = {
         field = nil,
        inherit = false,
         type = nil,
         field = 'tribe',
         func = function (tpl_args, frame)
         type = 'Integer',
            if tpl_args.seed_type ~= nil then
         func = h.proc.number,
                tpl_args.seed_type_html = m_util.html.poe_color(tpl_args.seed_type_id, tpl_args.seed_type)
            end
        end
     },
     },
     seed_effect = {
     tattoo_limit = {
         field = 'effect',
        inherit = false,
         type = 'Text',
         field = 'tattoo_limit',
         type = 'String',
        func = nil,
     },
     },
     seed_tier = {
     tattoo_min_adjacent = {
         field = 'tier',
        inherit = false,
         field = 'min_adjacent',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('seed_tier'),
         func = h.proc.number,
        default = 0,
     },
     },
     seed_growth_cycles = {
     tattoo_max_adjacent = {
         field = 'growth_cycles',
        inherit = false,
         field = 'max_adjacent',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('seed_growth_cycles'),
         func = h.proc.number,
        default = 0,
     },
     },
     seed_required_nearby_seed_tier = {
     tattoo_skill_id = {
         field = 'required_nearby_seed_tier',
        inherit = false,
         type = 'Integer',
         field = 'skill_id',
         func = m_util.cast.factory.number('seed_required_nearby_seed_tier'),
         type = 'String',
         func = nil,
     },
     },
     seed_required_nearby_seed_amount = {
     -- Delirium orbs
         field = 'required_nearby_seed_amount',
    is_delirium_orb = {
         type = 'Integer',
        inherit = false,
         func = m_util.cast.factory.number('seed_required_nearby_seed_amount'),
         field = nil,
         type = nil,
         func = function (tpl_args, value)
            tpl_args._flags.is_delirium_orb = tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'CurrencyAfflictionOrb', 1, true) ~= nil
            return value
        end,
     },
     },
     seed_consumed_wild_lifeforce_percentage = {
     -- Catalysts
         field = 'consumed_wild_lifeforce_percentage',
    is_catalyst = {
         type = 'Integer',
        inherit = false,
         func = m_util.cast.factory.number('seed_consumed_wild_lifeforce_percentage'),
         field = nil,
         default = 0,
         type = nil,
         func = function (tpl_args, value)
            tpl_args._flags.is_catalyst = m_util.table.contains(tpl_args.tags, 'catalyst')
            return value
         end,
     },
     },
     seed_consumed_vivid_lifeforce_percentage = {
     -- Cosmetic items
         field = 'consumed_vivid_lifeforce_percentage',
    cosmetic_type = {
         type = 'Integer',
        inherit = false,
         func = m_util.cast.factory.number('seed_consumed_vivid_lifeforce_percentage'),
         field = 'cosmetic_type',
         default = 0,
         type = 'String',
         func = h.proc.factory.value{
            validate = m_util.validate.factory.in_table_keys{
                tbl = m_game.constants.item.cosmetic_item_types,
                errmsg = i18n.errors.invalid_cosmetic_type,
            },
         },
     },
     },
     seed_consumed_primal_lifeforce_percentage = {
     cosmetic_theme = {
         field = 'consumed_primal_lifeforce_percentage',
         inherit = false,
         type = 'Integer',
         field = 'theme',
         func = m_util.cast.factory.number('seed_consumed_primal_lifeforce_percentage'),
         type = 'String',
         default = 0,
         func = nil,
     },
     },
     seed_granted_craft_option_ids = {
     cosmetic_target = {
         field = 'granted_craft_option_ids',
        inherit = false,
         type = 'List (,) of String',
         field = 'target',
         func = m_util.cast.factory.number('seed_grandted_craft_option_ids'),
         type = 'List (,) of String',
         default = 0,
         func = h.proc.list,
         default = {},
     },
     },
     --
     --
     -- harvest planet boosters
     -- Harvest seeds
     --
     --
     plant_booster_radius = {
     seed_type_id = {
         field = 'radius',
        inherit = false,
         type = 'Integer',
         field = 'type_id',
         func = m_util.cast.factory.number('plant_booster_radius'),
         type = 'String',
         func = nil,
     },
     },
     plant_booster_lifeforce = {
     seed_type = {
         field = 'lifeforce',
        inherit = false,
         type = 'Integer',
         field = 'type',
         func = m_util.cast.factory.number('plant_booster_lifeforce'),
         type = 'String',
         func = function (tpl_args, value)
            if tpl_args.seed_type_id ~= 'none' or tpl_args.seed_type_id ~= nil then
                value = m_game.seed_types[tpl_args.seed_type_id]
            end
            return value
        end,
     },
     },
     plant_booster_additional_crafting_options = {
     seed_type_html = {
         field = 'additional_crafting_options',
        inherit = false,
         type = 'Integer',
         field = nil,
         func = m_util.cast.factory.number('plant_booster_additional_crafting_options'),
         type = nil,
         func = function (tpl_args, value)
            if tpl_args.seed_type ~= nil then
                value = m_util.html.poe_color(tpl_args.seed_type_id, tpl_args.seed_type)
            end
            return value
        end,
     },
     },
     plant_booster_extra_chances = {
     seed_effect = {
         field = 'extra_chances',
        inherit = false,
        field = 'effect',
        type = 'Text',
        func = nil,
    },
    seed_tier = {
        inherit = false,
        field = 'tier',
        type = 'Integer',
        func = h.proc.number,
    },
    seed_growth_cycles = {
        inherit = false,
         field = 'growth_cycles',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('plant_booster_extra_chances'),
         func = h.proc.number,
     },
     },
     --
     seed_required_nearby_seed_tier = {
    -- Heist properties
        inherit = false,
    --
         field = 'required_nearby_seed_tier',
    heist_required_job_id = {
         type = 'Integer',
         field = 'required_job_id',
         func = h.proc.number,
         type = 'String',
         func = h.factory.cast_text('heist_required_job_id'),
     },
     },
     heist_required_job_level = {
     seed_required_nearby_seed_amount = {
         field = 'required_job_level',
        inherit = false,
         field = 'required_nearby_seed_amount',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('heist_required_job_level'),
         func = h.proc.number,
     },
     },
     heist_data = {
     seed_consumed_wild_lifeforce_percentage = {
         func = function (tpl_args, frame)
         inherit = false,
            if tpl_args.heist_required_job_level then
        field = 'consumed_wild_lifeforce_percentage',
                if tpl_args.heist_required_job_id then
        type = 'Integer',
                    local results = m_cargo.query(
        func = h.proc.number,
                        {'heist_jobs', 'heist_npcs', 'heist_npc_skills'},
        default = 0,
                        {'heist_npcs.name', 'heist_jobs.name'},
                        {
                            join = 'heist_npc_skills.job_id=heist_jobs.id, heist_npc_skills.npc_id=heist_npcs.id',
                            where = string.format('heist_npc_skills.job_id = "%s" AND heist_npc_skills.level >= %s', tpl_args.heist_required_job_id, tpl_args.heist_required_job_level),
                        }
                    )
                   
                    local npcs = {}
                   
                    for _, row in ipairs(results) do
                        npcs[#npcs+1] = row['heist_npcs.name']
                    end
                   
                    tpl_args.heist_required_npcs = table.concat(npcs, ', ')
                    tpl_args.heist_required_job = results[1]['heist_jobs.name']
                else
                    tpl_args.heist_required_job = i18n.tooltips.heist_any_job
                end
            end
        end,
     },
     },
     --
     seed_consumed_vivid_lifeforce_percentage = {
    -- hideout doodads (HideoutDoodads.dat)
        inherit = false,
     --
        field = 'consumed_vivid_lifeforce_percentage',
     is_master_doodad = {
        type = 'Integer',
         field = 'is_master_doodad',
        func = h.proc.number,
         type = 'Boolean',
        default = 0,
         func = m_util.cast.factory.boolean('is_master_doodad'),
     },
     seed_consumed_primal_lifeforce_percentage = {
        inherit = false,
         field = 'consumed_primal_lifeforce_percentage',
         type = 'Integer',
         func = h.proc.number,
        default = 0,
     },
     },
     master = {
     seed_granted_craft_option_ids = {
         field = 'master',
        inherit = false,
         type = 'String',
         field = 'granted_craft_option_ids',
        -- todo validate against list of master names
         type = 'List (,) of String',
         func = m_util.cast.factory.table('master', {key='full', tbl=m_game.constants.masters}),
         func = h.proc.list,
        default = {},
     },
     },
     master_level_requirement = {
     --
         field = 'level_requirement',
    -- Harvest planet boosters
    --
    plant_booster_radius = {
        inherit = false,
         field = 'radius',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('master_level_requirement'),
         func = h.proc.number,
     },
     },
     master_favour_cost = {
     plant_booster_lifeforce = {
         field = 'favour_cost',
        inherit = false,
         field = 'lifeforce',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('master_favour_cost'),
         func = h.proc.number,
     },
     },
     variation_count = {
     plant_booster_additional_crafting_options = {
         field = 'variation_count',
        inherit = false,
         field = 'additional_crafting_options',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('variation_count'),
         func = h.proc.number,
     },
     },
     -- Propehcy
    plant_booster_extra_chances = {
     prophecy_id = {
        inherit = false,
         field = 'prophecy_id',
        field = 'extra_chances',
        type = 'Integer',
        func = h.proc.number,
    },
    --
    -- Heist properties
     --
     heist_required_job_id = {
         field = 'required_job_id',
         type = 'String',
         type = 'String',
         func = nil,
         func = h.proc.text,
     },
     },
     prediction_text = {
     heist_required_job_level = {
         field = 'prediction_text',
         field = 'required_job_level',
        type = 'Text',
        func = h.factory.cast_text('prediction_text'),
    },
    seal_cost = {
        field = 'seal_cost',
         type = 'Integer',
         type = 'Integer',
         func = m_util.cast.factory.number('seal_cost'),
         func = h.proc.number,
     },
     },
     prophecy_reward = {
     heist_data = {
         field = 'reward',
        inherit = false,
         type = 'Text',
         field = nil,
         func = h.factory.cast_text('prophecy_reward'),
         type = nil,
    },
         func = function (tpl_args, value)
    prophecy_objective = {
            if tpl_args.heist_required_job_level then
        field = 'objective',
                if tpl_args.heist_required_job_id then
        type = 'Text',
                    local results = m_cargo.query(
      func = h.factory.cast_text('prophecy_objective'),
                        {'heist_npc_skills', 'heist_jobs', 'heist_npcs'},
    },
                        {'heist_npcs.name', 'heist_jobs.name'},
    -- Divination cards
                        {
    card_art = {
                            join = 'heist_npc_skills.job_id=heist_jobs.id, heist_npc_skills.npc_id=heist_npcs.id',
        field = 'card_art',
                            where = string.format('heist_npc_skills.job_id = "%s" AND heist_npc_skills.level >= %s', tpl_args.heist_required_job_id, tpl_args.heist_required_job_level),
        type = 'Page',
                        }
        func = function(tpl_args, frame)
                    )
            tpl_args.card_art = string.format(i18n.files.divination_card_art, tpl_args.card_art or tpl_args.name)
                    local npcs = {}
                    for _, row in ipairs(results) do
                        npcs[#npcs+1] = row['heist_npcs.name']
                    end
                    tpl_args.heist_required_npcs = table.concat(npcs, ', ')
                    tpl_args.heist_required_job = results[1]['heist_jobs.name']
                else
                    tpl_args.heist_required_job = i18n.tooltips.heist_any_job
                end
            end
            return value
         end,
         end,
     },
     },
     -- ------------------------------------------------------------------------
     --
     -- derived stats
     -- Hideout decorations
     -- ------------------------------------------------------------------------
     --
      
     is_master_doodad = {
     -- For rarity != normal, rarity already verified
        inherit = false,
     base_item = {
        field = 'is_master_doodad',
         no_copy = true,
        type = 'Boolean',
         field = 'base_item',
        func = h.proc.boolean,
         type = 'String',
        default = false,
         func = function(tpl_args, frame)
     },
             tpl_args.base_item = tpl_args.base_item_data['items.name']
     variation_count = {
         inherit = false,
         field = 'variation_count',
         type = 'Integer',
        func = h.proc.number,
    },
    --
    -- Prophecies
    --
    is_prophecy = {
        inherit = false,
        field = nil,
        type = nil,
         func = function (tpl_args, value)
             tpl_args._flags.is_prophecy = tpl_args.prophecy_id ~= nil
            return value
         end,
         end,
        default = false,
     },
     },
     base_item_id = {
     prophecy_id = {
         no_copy = true,
         inherit = false,
         field = 'base_item_id',
         field = 'prophecy_id',
         type = 'String',
         type = 'String',
         func = function(tpl_args, frame)
         func = nil,
            tpl_args.base_item_id = tpl_args.base_item_data['items.metadata_id']
        end,
     },
     },
     base_item_page = {
     prediction_text = {
         no_copy = true,
         inherit = false,
         field = 'base_item_page',
         field = 'prediction_text',
         type = 'Page',
         type = 'Text',
         func = function(tpl_args, frame)
         func = h.proc.text,
            tpl_args.base_item_page = tpl_args.base_item_data['items._pageName']
        end,
     },
     },
     name_list = {
     seal_cost = {
         no_copy = true,
         inherit = false,
         field = 'name_list',
         field = 'seal_cost',
         type = 'List (�) of String',
         type = 'Integer',
         func = function(tpl_args, frame)
         func = h.proc.number,
            if tpl_args.name_list ~= nil then
    },
                tpl_args.name_list = m_util.string.split(tpl_args.name_list, ',%s*')
    prophecy_reward = {
                tpl_args.name_list[#tpl_args.name_list+1] = tpl_args.name
        inherit = false,
             else
        field = 'reward',
                tpl_args.name_list = {tpl_args.name}
        type = 'Text',
            end
        func = h.proc.text,
    },
    prophecy_objective = {
        inherit = false,
        field = 'objective',
        type = 'Text',
        func = h.proc.text,
    },
    --
    -- Divination cards
    --
    card_art = {
        inherit = false,
        field = 'card_art',
        type = 'Page',
        func = function (tpl_args, value)
             return string.format(i18n.files.divination_card_art, value or tpl_args.name)
         end,
         end,
     },
     },
     frame_type = {
     card_background = {
         no_copy = true,
         inherit = false,
         field = 'frame_type',
         field = 'card_background',
         type = 'String',
         type = 'List (,) of Integer',
        property = nil,
         func = h.proc.factory.list{
         func = function(tpl_args, frame)
             callback = m_util.cast.number,
            if tpl_args._flags.is_prophecy then
        },
                tpl_args.frame_type = 'prophecy'
        default = {},
                return
            end
       
             local var = cfg.class_specifics[tpl_args.class_id]
            if var ~= nil and var.frame_type ~= nil then
                tpl_args.frame_type = var.frame_type
                return
            end
           
            if tpl_args.is_relic then
                tpl_args.frame_type = 'relic'
                return
            end
           
            tpl_args.frame_type = tpl_args.rarity_id
        end,
     },
     },
     --
     --
     -- args populated by mod validation
     -- Sentinels
     --  
     --
     mods = {
     sentinel_duration = {
         default = function (tpl_args, frame) return {} end,
         field = 'duration',
        func_fetch = function (tpl_args, frame)
        type = 'Integer',
            local results = m_cargo.query(
        func = h.proc.number,
                {'items' ,'item_mods'},
                {'item_mods.id', 'item_mods.is_implicit', 'item_mods.is_random', 'item_mods.text'},
                {
                    join = 'items._pageID=item_mods._pageID',
                    where = string.format('items._pageName="%s" AND item_mods.is_implicit=1', tpl_args.base_item_page),
                }
            )
            for _, row in ipairs(results) do
                -- Handle text-only mods
                local result
                if row['item_mods.id'] == nil then
                    result = row['item_mods.text']
                end
                tpl_args._mods[#tpl_args._mods+1] = {
                    result=result,
                    id=row['item_mods.id'],
                    stat_text=row['item_mods.text'],
                    is_implicit=m_util.cast.boolean(row['item_mods.is_implicit']),
                    is_random=m_util.cast.boolean(row['item_mods.is_random']),
                }
            end
        end,
     },
     },
     physical_damage_html = {
     sentinel_empowers = {
        no_copy = true,
         field = 'empowers',
         field = 'physical_damage_html',
         type = 'Integer',
         type = 'Text',
         func = h.proc.number,
         func = core.factory.damage_html{key='physical'},
     },
     },
     fire_damage_html = {
     sentinel_empowerment = {
        no_copy = true,
         field = 'empowerment',
         field = 'fire_damage_html',
         type = 'Integer',
         type = 'Text',
         func = h.proc.number,
         func = core.factory.damage_html{key='fire'},
     },
     },
     cold_damage_html = {
     sentinel_charge = {
        no_copy = true,
         field = 'charge',
         field = 'cold_damage_html',
         type = 'Integer',
         type = 'Text',
         func = h.proc.number,
         func = core.factory.damage_html{key='cold'},
     },
     },
     lightning_damage_html = {
     sentinel_monster = {
         no_copy = true,
         inherit = false,
         field = 'lightning_damage_html',
         field = 'monster',
         type = 'Text',
         type = 'String',
         func = core.factory.damage_html{key='lightning'},
         func = nil,
     },
     },
     chaos_damage_html = {
     sentinel_monster_level = {
         no_copy = true,
         inherit = false,
         field = 'chaos_damage_html',
         field = 'monster_level',
         type = 'Text',
         type = 'Integer',
         func = core.factory.damage_html{key='chaos'},
         func = h.proc.number,
     },
     },
     damage_avg = {
     --
         no_copy = true,
    -- Corpse items
         field = 'damage_avg',
    --
         type = 'Text',
    corpse_tier = {
         func = function(tpl_args, frame)
         inherit = false,
             local dmg = {min=0, max=0}
         field = 'tier',
            for key, _ in pairs(dmg) do
         type = 'Integer',
                 for _, dkey in ipairs(m_game.constants.damage_type_order) do
         func = function (tpl_args, value)
                     dmg[key] = dmg[key] + tpl_args[string.format('%s_damage_%s_range_average', dkey, key)]
             if tpl_args.metadata_id ~= nil then
                 for k, v in ipairs({'Low', 'Mid', 'High'}) do
                     if string.find(tpl_args.metadata_id, 'Metadata/Items/ItemisedCorpses/%w+' .. v) then
                        return k
                    end
                 end
                 end
             end
             end
              
             return nil
            dmg = (dmg.min + dmg.max) / 2
           
            tpl_args.damage_avg = dmg
         end,
         end,
     },
     },
     damage_html = {
     monster_category = {
         no_copy = true,
         inherit = false,
         field = 'damage_html',
         field = 'monster_category',
         type = 'Text',
         type = 'String',
         func = function(tpl_args, frame)
         func = h.proc.factory.value{
             local text = {}
             validate = m_util.validate.factory.in_table_keys{
            for _, dkey in ipairs(m_game.constants.damage_type_order) do
                tbl = m_game.constants.monster.categories,
                local value = tpl_args[dkey .. '_damage_html']
                 errmsg = i18n.errors.invalid_monster_category,
                 if value ~= nil then
             },
                    text[#text+1] = value
         },
                end
            end
            if #text > 0 then
                tpl_args.damage_html = table.concat(text, '<br>')
             end
         end,
     },
     },
     item_limit = {
     monster_category_html = {
         no_copy = true,
         inherit = false,
         field = 'item_limit',
         field = 'monster_category_html',
        type = 'Integer',
        func = m_util.cast.factory.number('item_limit'),
    },
    jewel_radius_html = {
        no_copy = true,
        field = 'radius_html',
         type = 'Text',
         type = 'Text',
         func = function(tpl_args, frame)
         func = function (tpl_args, value)
             -- Get radius from stats
             if tpl_args.monster_category ~= nil then
            local radius = tpl_args._stats.local_jewel_effect_base_radius
                 value = mw.html.create()
            if radius then
                    :tag('span')
                 radius = radius.min
                        :addClass('mon-cat -' .. m_game.constants.monster.categories[tpl_args.monster_category].long_lower)
                local size = m_game.constants.item.jewel_radius_to_size[radius] or radius
                        :done()
                local color =  radius == 0 and 'mod' or 'value'
                    :wikitext(m_game.constants.monster.categories[tpl_args.monster_category].long_upper)
                tpl_args.jewel_radius_html = m_util.html.poe_color(color, size)
                value = m_util.html.poe_color('value', tostring(value))
             end
             end
            return value
         end,
         end,
     },
     },
     incubator_effect = {
     monster_abilities = {
         no_copy = true,
         inherit = false,
         field = 'effect',
         field = 'monster_abilities',
         type = 'Text',
         type = 'Text',
         func = nil,
         func = nil,
     },
     },
     drop_areas_html = {
     --
         no_copy = true,
    -- Embers of the Allflame
         field = 'drop_areas_html',
    --
         type = 'Text',
    pack_id = {
         func = function(tpl_args, frame)
         inherit = false,
            if tpl_args.drop_areas_data == nil then
         field = 'pack_id',
                return
         type = 'String',
            end
         func = nil,
           
            if tpl_args.drop_areas_html ~= nil then
                return
            end
           
            local areas = {}
            for _, data in pairs(tpl_args.drop_areas_data) do
                -- skip legacy maps in the drop html listing
                if not string.match(data['areas.id'], '^Map.*') or string.match(data['areas.id'], '^MapWorlds.*') or string.match(data['areas.id'], '^MapAtziri.*') then
                    areas[#areas+1] = string.format('[[%s|%s]]', data['areas.main_page'] or data['areas._pageName'], data['areas.main_page'] or data['areas.name'])
                end
            end
           
            tpl_args.drop_areas_html = table.concat(areas, ' • ')
        end,
     },
     },
     release_version = {
     pack_size = {
         no_copy = true,
         inherit = false,
         field = 'release_version',
         field = 'pack_size',
         type = 'String'
         type = nil,
        func = nil,
     },
     },
     removal_version = {
     pack_min_size = {
         no_copy = true,
         inherit = false,
         field = 'removal_version',
         field = 'pack_min_size',
         type = 'String',
         type = 'Integer',
        func = h.proc.number,
        default = 1,
     },
     },
     --
     pack_max_size = {
    -- args governing use of the template itself
         inherit = false,
    --
         field = 'pack_max_size',
    suppress_improper_modifiers_category = {
        type = 'Integer',
         no_copy = true,
         func = h.proc.number,
         field = nil,
         default = 1,
         func = m_util.cast.factory.boolean('suppress_improper_modifiers_category'),
         default = false,
     },
     },
     upgraded_from_disabled = {
     pack_leader_chance = {
         no_copy = true,
         inherit = false,
         field = nil,
         field = 'pack_leader_chance',
         func = m_util.cast.factory.boolean('upgraded_from_disabled'),
         type = 'Float',
         default = false,
         func = h.proc.number,
     },
     },
}


core.stat_map = {
    -- ------------------------------------------------------------------------
     required_level_final = {
    -- derived stats
         field = 'required_level',
    -- ------------------------------------------------------------------------
         stats_add = {
   
            'local_level_requirement_+',
    -- Populated by processing base item
         },
     base_item_id = {
        stats_override = {
         inherit = false,
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=1, max=1},
         field = 'base_item_id',
         },
         type = 'String',
        minimum = 1,
         func = nil,
        html_fmt_options = {
            fmt = '%i',
        },
     },
     },
     weapon_range = {
     base_item_page = {
         field = 'weapon_range',
         inherit = false,
         stats_add = {
         field = 'base_item_page',
            'local_weapon_range_+',
         type = 'Page',
         },
         func = nil,
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
         },
     },
     },
     physical_damage_min = {
     base_item = {
         field = 'physical_damage_min',
         inherit = false,
         stats_add = {
         field = 'base_item',
            'local_minimum_added_physical_damage',
         type = 'String',
         },
         func = nil,
        stats_increased = {
            'local_physical_damage_+%',
            'quality',
        },
        stats_override = {
            ['local_weapon_no_physical_damage'] = {min=0, max=0},
        },
         minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
     },
     },
     physical_damage_max = {
     name_list = {
         field = 'physical_damage_max',
         inherit = false,
         stats_add = {
         field = 'name_list',
            'local_maximum_added_physical_damage',
         type = 'List (�) of String',
         },
         func = function (tpl_args, value)
        stats_increased = {
            value = m_util.cast.table(value)
            'local_physical_damage_+%',
             value[#value+1] = tpl_args.name
            'quality',
            return value
         },
         end,
        stats_override = {
         default = {},
             ['local_weapon_no_physical_damage'] = {min=0, max=0},
        },
         minimum = 0,
         html_fmt_options = {
            fmt = '%i',
        },
     },
     },
     fire_damage_min = {
     frame_type = {
         field = 'fire_damage_min',
        inherit = false,
         stats_add = {
         field = 'frame_type',
            'local_minimum_added_fire_damage',
         type = 'String',
         },
         func = function (tpl_args, value)
        minimum = 0,
            if value then
        html_fmt_options = {
                return value
             color = 'fire',
            end
             fmt = '%i',
             if tpl_args._flags.is_prophecy then
         },
                return 'prophecy'
             end
            local var = cfg.class_specifics[tpl_args.class_id]
            if var ~= nil and var.frame_type ~= nil then
                return var.frame_type
            end
            return tpl_args.rarity_id
         end,
     },
     },
     fire_damage_max = {
     --
         field = 'fire_damage_max',
    -- args populated by mod validation
         stats_add = {
    --
            'local_maximum_added_fire_damage',
    physical_damage_html = {
         },
         inherit = false,
        minimum = 0,
         field = 'physical_damage_html',
         html_fmt_options = {
         type = 'Text',
            color = 'fire',
         func = h.proc.factory.damage_html{type = 'physical'},
            fmt = '%i',
        },
     },
     },
     cold_damage_min = {
     fire_damage_html = {
         field = 'cold_damage_min',
         inherit = false,
         stats_add = {
         field = 'fire_damage_html',
            'local_minimum_added_cold_damage',
         type = 'Text',
         },
         func = h.proc.factory.damage_html{type = 'fire'},
        minimum = 0,
         html_fmt_options = {
            color = 'cold',
            fmt = '%i',
        },
     },
     },
     cold_damage_max = {
     cold_damage_html = {
         field = 'cold_damage_max',
         inherit = false,
         stats_add = {
         field = 'cold_damage_html',
            'local_maximum_added_cold_damage',
         type = 'Text',
         },
         func = h.proc.factory.damage_html{type = 'cold'},
        minimum = 0,
         html_fmt_options = {
            color = 'cold',
            fmt = '%i',
        },
     },
     },
     lightning_damage_min = {
     lightning_damage_html = {
         field = 'lightning_damage_min',
         inherit = false,
         stats_add = {
         field = 'lightning_damage_html',
            'local_minimum_added_lightning_damage',
         type = 'Text',
         },
         func = h.proc.factory.damage_html{type = 'lightning'},
        minimum = 0,
         html_fmt_options = {
            color = 'lightning',
            fmt = '%i',
        },
     },
     },
     lightning_damage_max = {
     chaos_damage_html = {
         field = 'lightning_damage_max',
         inherit = false,
         stats_add = {
         field = 'chaos_damage_html',
            'local_maximum_added_lightning_damage',
         type = 'Text',
         },
         func = h.proc.factory.damage_html{type = 'chaos'},
        minimum = 0,
         html_fmt_options = {
            color = 'lightning',
            fmt = '%i',
        },
     },
     },
     chaos_damage_min = {
     damage_avg = {
         field = 'chaos_damage_min',
        inherit = false,
         stats_add = {
         field = 'damage_avg',
            'local_minimum_added_chaos_damage',
         type = 'Text',
         },
         func = function (tpl_args, value)
        minimum = 0,
            local min = 0
        html_fmt_options = {
            local max = 0
             color = 'chaos',
             for _, damage_type in ipairs(m_game.constants.damage_type_order) do
            fmt = '%i',
                min = min + tpl_args[damage_type .. '_damage_min_range_average']
         },
                max = max + tpl_args[damage_type .. '_damage_max_range_average']
            end
            value = (min + max) / 2
            return value
         end,
     },
     },
     chaos_damage_max = {
     damage_html = {
         field = 'chaos_damage_max',
        inherit = false,
         stats_add = {
         field = 'damage_html',
            'local_maximum_added_chaos_damage',
         type = 'Text',
         },
         func = function (tpl_args, value)
        minimum = 0,
            local text = {}
        html_fmt_options = {
             for _, dkey in ipairs(m_game.constants.damage_type_order) do
             color = 'chaos',
                local range = tpl_args[dkey .. '_damage_html']
             fmt = '%i',
                if range ~= nil then
         },
                    text[#text+1] = range
                end
            end
             if #text > 0 then
                value = table.concat(text, '<br>')
            end
            return value
         end,
     },
     },
     critical_strike_chance = {
     jewel_radius_html = {
         field = 'critical_strike_chance',
        inherit = false,
         stats_add = {
         field = 'radius_html',
            'local_critical_strike_chance',
         type = 'Text',
         },
         func = function (tpl_args, value)
        stats_increased = {
            -- Get radius from stats
             'local_critical_strike_chance_+%',
            local radius = tpl_args._stats.local_jewel_effect_base_radius
        },
             if radius then
        stats_override = {
                radius = radius.min
            ['local_weapon_always_crit'] = {min=100, max=100},
                local size = m_game.constants.item.jewel_radius_to_size[radius] or radius
        },
                local color = radius == 0 and 'mod' or 'value'
        minimum = 0,
                value = m_util.html.poe_color(color, size)
        html_fmt_options = {
            end
             fmt = '%.2f%%',
             return value
         },
         end,
     },
     },
     attack_speed = {
     drop_areas_html = {
         field = 'attack_speed',
        inherit = false,
         stats_increased = {
         field = 'drop_areas_html',
            'local_attack_speed_+%',
         type = 'Text',
         },
         func = function (tpl_args, value)
        minimum = 0,
            if tpl_args._drop_areas_data == nil then
        html_fmt_options = {
                return value
             fmt = '%.2f',
            end
         },
            if value ~= nil then
                return value
            end
            local areas = {}
             for _, data in pairs(tpl_args._drop_areas_data) do
                -- skip legacy maps in the drop html listing
                if not string.match(data.id, '^Map.+') or string.match(data.id, '^MapWorlds.+') or string.match(data.id, '^MapAtziri.+') then
                    areas[#areas+1] = string.format('[[%s|%s]]', data.main_page or data._pageName, data.main_page or data.name)
                end
            end
            return table.concat(areas, ' • ')
         end,
     },
     },
     flask_life = {
     release_version = {
         field = 'life',
         inherit = false,
         stats_add = {
         field = 'release_version',
            'local_flask_life_to_recover',
         type = 'String',
         },
         func = nil,
        stats_increased = {
            'local_flask_life_to_recover_+%',
            'local_flask_amount_to_recover_+%',
            'quality',
         },
        html_fmt_options = {
            fmt = '%i',
        },
     },
     },
     flask_mana = {
     removal_version = {
         field = 'mana',
        inherit = false,
        field = 'removal_version',
        type = 'String',
        func = nil,
    },
    --
    -- args governing use of the template itself
    --
    suppress_improper_modifiers_category = {
        inherit = false,
        field = nil,
        func = h.proc.boolean,
        default = false,
    },
    disable_automatic_recipes = {
        inherit = false,
        field = nil,
        func = h.proc.boolean,
        default = false,
    },
}
 
core.stat_map = {
    required_level_final = {
         field = 'required_level',
         stats_add = {
         stats_add = {
             'local_flask_mana_to_recover',
             'local_level_requirement_+',
         },
         },
         stats_increased = {
         minimum = 1,
             'local_flask_mana_to_recover_+%',
        html_fmt_options = {
            'local_flask_amount_to_recover_+%',
             fmt = '%i',
            'quality',
         },
         },
     },
     },
     flask_duration = {
     weapon_range = {
         field = 'duration',
         field = 'weapon_range',
         stats_increased = {
         stats_add_distance = {
             'local_flask_duration_+%',
             'local_weapon_range_+',
            -- regular quality isn't used here because it doesn't increase duration of life/mana/hybrid flasks
            'quality_flask_duration',
        },
        stats_increased_inverse = {
            'local_flask_recovery_speed_+%',
         },
         },
         minimum = 0,
         minimum = 0,
         html_fmt_options = {
         html_fmt_options = {
             fmt = '%.2f',
             fmt = '%.1f',
         },
         },
     },
     },
     charges_per_use = {
     physical_damage_min = {
         field = 'charges_per_use',
         field = 'physical_damage_min',
        stats_add = {
            'local_minimum_added_physical_damage',
        },
         stats_increased = {
         stats_increased = {
             'local_charges_used_+%',
             'local_physical_damage_+%',
            'quality',
        },
        stats_override = {
            ['local_weapon_no_physical_damage'] = {min=0, max=0},
         },
         },
         minimum = 0,
         minimum = 0,
Line 2,169: Line 2,398:
         },
         },
     },
     },
     charges_max = {
     physical_damage_max = {
         field = 'charges_max',
         field = 'physical_damage_max',
         stats_add = {
         stats_add = {
             'local_extra_max_charges',
             'local_maximum_added_physical_damage',
         },
         },
         stats_increased = {
         stats_increased = {
             'local_max_charges_+%',
             'local_physical_damage_+%',
            'quality',
        },
        stats_override = {
            ['local_weapon_no_physical_damage'] = {min=0, max=0},
         },
         },
         minimum = 0,
         minimum = 0,
Line 2,182: Line 2,415:
         },
         },
     },
     },
     block = {
     fire_damage_min = {
         field = 'block',
         field = 'fire_damage_min',
         stats_add = {
         stats_add = {
             'local_additional_block_chance_%',
             'local_minimum_added_fire_damage',
         },
         },
         minimum = 0,
         minimum = 0,
         html_fmt_options = {
         html_fmt_options = {
             fmt = '%i%%',
             fmt = '%i',
            color = 'fire',
         },
         },
     },
     },
     armour = {
     fire_damage_max = {
         field = 'armour',
         field = 'fire_damage_max',
         stats_add = {
         stats_add = {
             'local_base_physical_damage_reduction_rating',
             'local_maximum_added_fire_damage',
         },
         },
         stats_increased = {
         minimum = 0,
             'local_physical_damage_reduction_rating_+%',
        html_fmt_options = {
             'local_armour_and_energy_shield_+%',
             fmt = '%i',
            'local_armour_and_evasion_+%',
             color = 'fire',
            'local_armour_and_evasion_and_energy_shield_+%',
        },
             'quality',
    },
    cold_damage_min = {
        field = 'cold_damage_min',
        stats_add = {
             'local_minimum_added_cold_damage',
         },
         },
         minimum = 0,
         minimum = 0,
         html_fmt_options = {
         html_fmt_options = {
             fmt = '%i',
             fmt = '%i',
            color = 'cold',
         },
         },
     },
     },
     evasion = {
     cold_damage_max = {
         field = 'evasion',
         field = 'cold_damage_max',
         stats_add = {
         stats_add = {
             'local_base_evasion_rating',
             'local_maximum_added_cold_damage',
            'local_evasion_rating_and_energy_shield',
        },
        stats_increased = {
            'local_evasion_rating_+%',
            'local_evasion_and_energy_shield_+%',
            'local_armour_and_evasion_+%',
            'local_armour_and_evasion_and_energy_shield_+%',
            'quality',
         },
         },
         minimum = 0,
         minimum = 0,
         html_fmt_options = {
         html_fmt_options = {
             fmt = '%i',
             fmt = '%i',
            color = 'cold',
         },
         },
     },
     },
     energy_shield = {
     lightning_damage_min = {
         field = 'energy_shield',
         field = 'lightning_damage_min',
         stats_add = {
         stats_add = {
             'local_energy_shield',
             'local_minimum_added_lightning_damage',
            'local_evasion_rating_and_energy_shield',
         },
         },
         stats_increased = {
         minimum = 0,
            'local_energy_shield_+%',
        html_fmt_options = {
            'local_armour_and_energy_shield_+%',
             fmt = '%i',
             'local_evasion_and_energy_shield_+%',
             color = 'lightning',
            'local_armour_and_evasion_and_energy_shield_+%',
             'quality',
         },
         },
         stats_override = {
    },
             ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
    lightning_damage_max = {
        field = 'lightning_damage_max',
         stats_add = {
             'local_maximum_added_lightning_damage',
         },
         },
         minimum = 0,
         minimum = 0,
         html_fmt_options = {
         html_fmt_options = {
             fmt = '%i',
             fmt = '%i',
            color = 'lightning',
         },
         },
     },
     },
     required_dexterity = {
     chaos_damage_min = {
         field = 'required_dexterity',
         field = 'chaos_damage_min',
         stats_add = {
         stats_add = {
             'local_dexterity_requirement_+'
             'local_minimum_added_chaos_damage',
         },
         },
         stats_increased = {
         minimum = 0,
             'local_dexterity_requirement_+%',
        html_fmt_options = {
             'local_attribute_requirements_+%',
             fmt = '%i',
             color = 'chaos',
         },
         },
        stats_override = {
    },
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
    chaos_damage_max = {
             ['local_no_attribute_requirements'] = {min=0, max=0},
        field = 'chaos_damage_max',
        stats_add = {
             'local_maximum_added_chaos_damage',
         },
         },
         minimum = 0,
         minimum = 0,
         html_fmt_options = {
         html_fmt_options = {
             fmt = '%i',
             fmt = '%i',
            color = 'chaos',
         },
         },
     },
     },
     required_intelligence = {
     critical_strike_chance = {
         field = 'required_intelligence',
         field = 'critical_strike_chance',
         stats_add = {
         stats_add = {
             'local_intelligence_requirement_+'
             'local_critical_strike_chance',
         },
         },
         stats_increased = {
         stats_increased = {
             'local_intelligence_requirement_+%',
             'local_critical_strike_chance_+%',
            'local_attribute_requirements_+%',
         },
         },
         stats_override = {
         stats_override = {
             ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
             ['local_weapon_crit_chance_is_100'] = {min=100, max=100},
            ['local_no_attribute_requirements'] = {min=0, max=0},
         },
         },
         minimum = 0,
         minimum = 0,
         html_fmt_options = {
         html_fmt_options = {
             fmt = '%i',
             fmt = '%.2f',
            inline = '%s%%',
        },
    },
    attack_speed = {
        field = 'attack_speed',
        stats_increased = {
            'local_attack_speed_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%.2f',
         },
         },
     },
     },
     required_strength = {
     flask_life = {
         field = 'required_strength',
         field = 'life',
         stats_add = {
         stats_add = {
             'local_strength_requirement_+'
             'local_flask_life_to_recover',
         },
         },
         stats_increased = {
         stats_increased = {
             'local_strength_requirement_+%',
             'local_flask_life_to_recover_+%',
             'local_attribute_requirements_+%',
             'local_flask_amount_to_recover_+%',
        },
             'quality',
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
             ['local_no_attribute_requirements'] = {min=0, max=0},
         },
         },
        minimum = 0,
         html_fmt_options = {
         html_fmt_options = {
             fmt = '%i',
             fmt = '%i',
         },
         },
     },
     },
     map_area_level = {
     flask_mana = {
         field = 'map_area_level',
         field = 'mana',
         stats_override = {
         stats_add = {
             ['map_item_level_override'] = true,
             'local_flask_mana_to_recover',
         },
         },
    },
        stats_increased = {
}
            'local_flask_mana_to_recover_+%',
 
             'local_flask_amount_to_recover_+%',
core.dps_map = {
             'quality',
    {
        name = 'physical_dps',
        field = 'physical_dps',
        damage_args = {'physical_damage', },
        label_infobox = i18n.tooltips.physical_dps,
        html_fmt_options = {
             color = 'value',
             fmt = '%.1f',
         },
         },
    },
    {
        name = 'fire_dps',
        field = 'fire_dps',
        damage_args = {'fire_damage'},
        label_infobox = i18n.tooltips.fire_dps,
         html_fmt_options = {
         html_fmt_options = {
            color = 'fire',
             fmt = '%i',
             fmt = '%.1f',
         },
         },
     },
     },
     {
     flask_duration = {
         name = 'cold_dps',
         field = 'duration',
         field = 'cold_dps',
         stats_increased = {
         damage_args = {'cold_damage'},
            'local_flask_duration_+%',
         label_infobox = i18n.tooltips.cold_dps,
            -- regular quality isn't used here because it doesn't increase duration of life/mana/hybrid flasks
            'quality_flask_duration',
         },
        stats_increased_inverse = {
            'local_flask_recovery_speed_+%',
        },
         minimum = 0,
         html_fmt_options = {
         html_fmt_options = {
            color = 'cold',
             fmt = '%.2f',
             fmt = '%.1f',
         },
         },
     },
     },
     {
     charges_per_use = {
         name = 'lightning_dps',
        field = 'charges_per_use',
         field = 'lightning_dps',
        stats_increased = {
         damage_args = {'lightning_damage'},
            'local_charges_used_+%',
         label_infobox = i18n.tooltips.lightning_dps,
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    charges_max = {
        field = 'charges_max',
        stats_add = {
            'local_extra_max_charges',
        },
        stats_increased = {
            'local_max_charges_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    armour = {
        arg = {
            min = 'armour_min',
            max = 'armour_max',
        },
        field = 'armour',
        stats_add = {
            'local_base_physical_damage_reduction_rating',
        },
        stats_increased = {
            'local_physical_damage_reduction_rating_+%',
            'local_armour_and_energy_shield_+%',
            'local_armour_and_evasion_+%',
            'local_armour_and_evasion_and_energy_shield_+%',
            'quality',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    evasion = {
        arg = {
            min = 'evasion_min',
            max = 'evasion_max',
        },
        field = 'evasion',
        stats_add = {
            'local_base_evasion_rating',
            'local_evasion_rating_and_energy_shield',
        },
        stats_increased = {
            'local_evasion_rating_+%',
            'local_evasion_and_energy_shield_+%',
            'local_armour_and_evasion_+%',
            'local_armour_and_evasion_and_energy_shield_+%',
            'quality',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    energy_shield = {
        arg = {
            min = 'energy_shield_min',
            max = 'energy_shield_max',
         },
        field = 'energy_shield',
        stats_add = {
            'local_energy_shield',
            'local_evasion_rating_and_energy_shield',
        },
        stats_increased = {
            'local_energy_shield_+%',
            'local_armour_and_energy_shield_+%',
            'local_evasion_and_energy_shield_+%',
            'local_armour_and_evasion_and_energy_shield_+%',
            'quality',
        },
        stats_override = {
            ['local_no_energy_shield'] = {min=0, max=0},
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    ward = {
        arg = {
            min = 'ward_min',
            max = 'ward_max',
        },
        field = 'ward',
        stats_add = {
            'local_ward',
        },
        stats_increased = {
            'local_ward_+%',
            'quality',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    block = {
        field = 'block',
        stats_add = {
            'local_additional_block_chance_%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
            inline = '%s%%',
        },
    },
    required_dexterity = {
        field = 'required_dexterity',
        stats_add = {
            'local_dexterity_requirement_+'
        },
        stats_increased = {
            'local_dexterity_requirement_+%',
            'local_attribute_requirements_+%',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
            ['local_no_attribute_requirements'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    required_intelligence = {
        field = 'required_intelligence',
        stats_add = {
            'local_intelligence_requirement_+'
        },
        stats_increased = {
            'local_intelligence_requirement_+%',
            'local_attribute_requirements_+%',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
            ['local_no_attribute_requirements'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    required_strength = {
        field = 'required_strength',
        stats_add = {
            'local_strength_requirement_+'
        },
        stats_increased = {
            'local_strength_requirement_+%',
            'local_attribute_requirements_+%',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
            ['local_no_attribute_requirements'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    map_area_level = {
        field = 'map_area_level',
        stats_override = {
            ['map_item_level_override'] = true,
        },
    },
    sentinel_duration = {
        field = 'duration',
        stats_add = {
            'local_sentinel_duration_+',
        },
        stats_increased = {
            'local_sentinel_drone_duration_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    sentinel_empowers = {
        field = 'empowers',
        stats_add = {
            'local_sentinel_tag_limit_+',
        },
        stats_increased = {
            'local_sentinel_tag_limit_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    sentinel_empowerment = {
        field = 'empowerment',
        stats_add = {
            'local_sentinel_drone_difficulty_+',
        },
        stats_increased = {
            'local_sentinel_drone_difficulty_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    sentinel_charge = {
        field = 'charge',
        stats_add = {
            'local_sentinel_drone_charge_+',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    pack_size = {
        arg = {
            min = 'pack_min_size',
            max = 'pack_max_size',
        },
        field = 'pack_size',
        minimum = 1,
        html_fmt_options = {
            fmt = '%i',
            fmt_range = '%s-%s',
        },
    },
}
 
core.dps_map = {
    physical_dps = {
         field = 'physical_dps',
         damage_args = {'physical_damage'},
         label_infobox = i18n.tooltips.physical_dps,
         html_fmt_options = {
         html_fmt_options = {
            color = 'lightning',
             fmt = '%.1f',
             fmt = '%.1f',
         },
            color = 'value',
     },
         },
     {
     },
         name = 'chaos_dps',
     fire_dps = {
         field = 'chaos_dps',
         field = 'fire_dps',
         damage_args = {'chaos_damage'},
        damage_args = {'fire_damage'},
         label_infobox = i18n.tooltips.chaos_dps,
        label_infobox = i18n.tooltips.fire_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'fire',
        },
    },
    cold_dps = {
        field = 'cold_dps',
        damage_args = {'cold_damage'},
        label_infobox = i18n.tooltips.cold_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'cold',
        },
    },
    lightning_dps = {
        field = 'lightning_dps',
        damage_args = {'lightning_damage'},
        label_infobox = i18n.tooltips.lightning_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'lightning',
        },
    },
    chaos_dps = {
         field = 'chaos_dps',
         damage_args = {'chaos_damage'},
         label_infobox = i18n.tooltips.chaos_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'chaos',
        },
    },
    elemental_dps = {
        field = 'elemental_dps',
        damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'},
        label_infobox = i18n.tooltips.elemental_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'value',
        },
    },
    poison_dps = {
        field = 'poison_dps',
        damage_args = {'physical_damage', 'chaos_damage'},
        label_infobox = i18n.tooltips.poison_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'value',
        },
    },
    dps = {
        field = 'dps',
        damage_args = {'physical_damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'},
        label_infobox = i18n.tooltips.dps,
         html_fmt_options = {
         html_fmt_options = {
            color = 'chaos',
             fmt = '%.1f',
             fmt = '%.1f',
        },
    },
    {
        name = 'elemental_dps',
        field = 'elemental_dps',
        damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'},
        label_infobox = i18n.tooltips.elemental_dps,
        html_fmt_options = {
             color = 'value',
             color = 'value',
            fmt = '%.1f',
        },
    },
    {
        name = 'poison_dps',
        field = 'poison_dps',
        damage_args = {'physical_damage', 'chaos_damage'},
        label_infobox = i18n.tooltips.poison_dps,
        html_fmt_options = {
            color = 'value',
            fmt = '%.1f',
        },
    },
    {
        name = 'dps',
        field = 'dps',
        damage_args = {'physical_damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'},
        label_infobox = i18n.tooltips.dps,
        html_fmt_options = {
            color = 'value',
            fmt = '%.1f',
         },
         },
     },
     },

Revision as of 08:23, 2 May 2024

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


Lua logo

This module depends on the following other modules:

This submodule contains core configuration and functions for use in Module:Item2 and its other submodules.


-------------------------------------------------------------------------------
-- 
-- Core confirguation and functions for Module:Item2 and submodules
-- 
-------------------------------------------------------------------------------

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('Item2')

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

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

local i18n = cfg.i18n

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

local h = {}

-- ----------------------------------------------------------------------------
-- Core
-- ----------------------------------------------------------------------------

local core = {}

core.factory = {}

function core.factory.infobox_line(args)
    -- args: table
    --  type: How to read data from tpl_args using the given keys. nil = Regular, gem = Gem progression, stat = Stats
    --  parts: table
    --   [n]: table
    --    key: key to use. If type = gem and table is given, parse for subfield along path
    --    hide: Hide part if this function returns true
    --    hide_key: Alternate key to use to retrieve the value
    --    hide_default: hide the value if this is set
    --    hide_default_key: key to use if it isn't equal to the key parameter
    --    ----- params from m_util.html.format_value -----
    --    func: Function to transform the value retrieved from the database
    --    fmt: Format string (or function that returns format string) to use for the value.
    --      Default: '%s'
    --    fmt_range: Format string to use for range value.
    --      Default: '(%s-%s)'
    --    color: poe_color code to use for the value. False for no color.
    --      Default: 'value' if value is unmodified; 'mod' if modified
    --    class: Additional css class added to color tag
    --    inline: Format string to use for the output
    --    inline_color: poe_color code to use for the output. False for no color.
    --      Default: Inherits from value color
    --    inline_class: Additional css class added to inline color tag
    --  sep: If specified, parts are joined with this separator before being formatted for output
    --  fmt: Format string to use for output. If not specified, parts are simply concatenated
    --  color: poe_color code to use for output. Default: no color
    --  class: Additional css class added to output

    args.parts = args.parts or {}
    return function (tpl_args)
        local base_values = {}
        local temp_values = {}
        if args.type == 'gem' then
            -- Skill progression. Look for keys in tpl_args.skill_levels
            if not cfg.class_groups.gems.keys[tpl_args.class_id] then
                -- Skip if this item is not actually a gem
                return
            end
            for i, data in ipairs(args.parts) do
                if data.key then
                    local path = type(data.key) == 'table' and data.key or {data.key}
                    -- Check for static value
                    local value = tpl_args.skill_levels[0]
                    for _, p in ipairs(path) do -- Parse for subfield along path
                        if value[p] == nil then
                            value = nil
                            break
                        else
                            value = value[p]
                        end
                    end
                    if value ~= nil then
                        base_values[i] = value
                        temp_values[#temp_values+1] = {value={min=value,max=value}, index=i}
                    else -- Check for leveled values
                        value = {
                            min = tpl_args.skill_levels[1],
                            max = tpl_args.skill_levels[tpl_args.max_level],
                        }
                        for k, _ in pairs(value) do
                            for _, p in ipairs(path) do -- Parse for subfield along path
                                if value[k][p] == nil then
                                    value[k] = nil
                                    break
                                else
                                    value[k] = value[k][p]
                                end
                            end
                        end
                        if value.min ~= nil and value.max ~= nil then
                            base_values[i] = value.min
                            temp_values[#temp_values+1] = {value=value, index=i}
                        end
                    end
                end
            end
        elseif args.type == 'stat' then
            -- Stats. Look for key in tpl_args._stats
            for i, data in ipairs(args.parts) do
                local stat = tpl_args._stats[data.key]
                if stat then
                    local total = {min=0, max=0}
                    for _, v in ipairs(stat) do
                        if v.mod == nil or v.mod.is_implicit or v.mod.is_explicit then
                            total.min = total.min + v.min
                            total.max = total.max + v.max
                        end
                    end
                    base_values[i] = total.min
                    temp_values[#temp_values+1] = {value=total, index=i}
                end
            end
        else
            -- Regular. Look for key exactly as written in tpl_args
            for i, data in ipairs(args.parts) do
                local value = {}
                if tpl_args[data.key .. '_range_minimum'] ~= nil then
                    value.min = tpl_args[data.key .. '_range_minimum']
                    value.max = tpl_args[data.key .. '_range_maximum']
                elseif tpl_args[data.key] ~= nil then
                    value.min = tpl_args[data.key]
                    value.max = tpl_args[data.key]
                end
                if value.min ~= nil and value.max ~= nil then
                    base_values[i] = value.min
                    temp_values[#temp_values+1] = {value=value, index=i}
                end
            end
        end
        
        local final_values = {}
        for i, data in ipairs(temp_values) do
            local opt = args.parts[data.index]
            local hide = false
            if type(opt.hide) == 'function' then
                local v = data.value
                if opt.hide_key then
                    v = {
                        min = tpl_args[opt.hide_key .. '_range_minimum'],
                        max = tpl_args[opt.hide_key .. '_range_maximum'],
                    }
                    if v.min == nil or v.max == nil then
                        v = tpl_args[opt.hide_key]
                    end
                end
                hide = opt.hide(tpl_args, v)
            elseif opt.hide_default ~= nil then
                if opt.hide_default_key then
                    local v = {
                        min = tpl_args[opt.hide_default_key .. '_range_minimum'],
                        max = tpl_args[opt.hide_default_key .. '_range_maximum'],
                    }
                    if v.min == nil or v.max == nil then
                        if opt.hide_default == tpl_args[opt.hide_default_key] then
                            hide = true
                        end
                    elseif opt.hide_default == v.min and opt.hide_default == v.max then
                        hide = true
                    end
                else
                    local v = data.value
                    if opt.hide_default == v.min and opt.hide_default == v.max then
                        hide = true
                    end
                end
            end            
            if not hide then
                table.insert(final_values, data)
            end
        end
        
        -- all zeros = dont display and return early
        if #final_values == 0 then
            return nil
        end
        
        local parts = {}
        for i, data in ipairs(final_values) do
            local value = data.value
            value.base = base_values[data.index]
            local options = args.parts[data.index]
            if args.type == 'gem' and options.color == nil then
                -- Display skill progression range values as unmodified (white)
                options.color = 'value'
            end
            parts[#parts+1] = m_util.html.format_value(tpl_args, value, options)
        end
        if args.sep then
            -- Join parts with separator before formatting
            parts = {table.concat(parts, args.sep)}
        end

        -- Build output string
        local out
        if args.fmt then
            out = string.format(args.fmt, unpack(parts))
        else
            out = table.concat(parts)
        end
        if args.color then
            out = m_util.html.poe_color(args.color, out, args.class)
        elseif args.class then
            out = tostring(mw.html.create('em')
                :attr('class', class)
                :wikitext(out)
            )
        end
        return out
    end
end

function core.add_stat(tpl_args, stat_id, value, options)
    options = options or {}
    local mod = options.mod
    local tbl = options.tbl or '_stats'
    tpl_args[tbl] = tpl_args[tbl] or {}
    tpl_args[tbl][stat_id] = tpl_args[tbl][stat_id] or {
        min = 0,
        max = 0,
        avg = 0,
    }
    local stat = {
        mod = mod,
    }
    if type(value) == 'table' then
        stat.min = value.min
        stat.max = value.max
        stat.avg = value.avg or (stat.min + stat.max) / 2
    else
        stat.min = value
        stat.max = value
        stat.avg = value
    end
    table.insert(tpl_args[tbl][stat_id], stat)
    -- Totals
    tpl_args[tbl][stat_id].min = tpl_args[tbl][stat_id].min + stat.min
    tpl_args[tbl][stat_id].max = tpl_args[tbl][stat_id].max + stat.max
    tpl_args[tbl][stat_id].avg = tpl_args[tbl][stat_id].avg + stat.avg
end

--
-- Functions for processing tpl_args
--
h.proc = {}
h.proc.factory = {}

function h.proc.factory.value(args)
    args = args or {}
    return function (tpl_args, value)
        if value == nil then
            return nil
        end
        if args.cast then
            value = args.cast(value)
        end
        if args.validate then
            value = args.validate(value)
        end
        return value
    end
end

function h.proc.factory.list(args)
    args = args or {}
    return function (tpl_args, value)
        return m_util.cast.table(value, {
            pattern = args.pattern,
            callback = args.callback,
        })
    end
end

function h.proc.factory.damage_html(args)
    return function (tpl_args, value)
        local keys = {
            min = args.type .. '_damage_min',
            max = args.type .. '_damage_max',
        }
        local range = {}
        for ktype, key in pairs(keys) do
            range[ktype] = core.factory.infobox_line{
                parts = {
                    {
                        key = key,
                        color = false,
                        hide_default = 0,
                    }
                }
            }(tpl_args)
        end
        if range.min and range.max then
            local color = args.type or false
            local range_fmt
            if tpl_args[keys.min .. '_range_minimum'] ~= tpl_args[keys.min .. '_range_maximum'] or tpl_args[keys.max .. '_range_minimum'] ~= tpl_args[keys.max .. '_range_maximum'] then
                -- Variable damage range, based on modifier rolls
                if args.type == 'physical' then
                    color = 'mod'
                end
                range_fmt = i18n.fmt.variable_damage_range
            else
                -- Standard damage range
                if args.type == 'physical' then
                    color = 'value'
                end
                range_fmt = i18n.fmt.standard_damage_range
            end
            value = string.format(range_fmt, range.min, range.max)
            if color then
                value = m_util.html.poe_color(color, value)
            end
        end
        return value
    end
end

function h.proc.factory.stat_text(args)
    return function (tpl_args, value)
        local type_map = {
            implicit = 'is_implicit',
            explicit = 'is_explicit',
        }
        if type_map[args.type] == nil then
            return nil
        end
        local lines = {}
        local random_mods = {}
        local skip = cfg.class_specifics[tpl_args.class_id] and cfg.class_specifics[tpl_args.class_id].skip_stat_lines or nil
        for _, mod_data in ipairs(tpl_args._mods) do
            if mod_data[type_map[args.type]] then
                if mod_data.is_random == true then
                    random_mods[mod_data.stat_text] = random_mods[mod_data.stat_text] or {}
                    table.insert(random_mods[mod_data.stat_text], mod_data)
                else
                    if mod_data.stat_text ~= nil then
                        table.insert(lines, mod_data.stat_text)
                    else
                        local text = mod_data.result['mods.stat_text']
                        if text and text ~= '' then
                            for _, line in ipairs(m_util.string.split(text, '<br>')) do
                                local skipped = false
                                if skip then
                                    for _, pattern in ipairs(skip) do
                                        if string.match(line, pattern) then
                                            skipped = true
                                            break
                                        end
                                    end
                                end
                                if not skipped then
                                    table.insert(lines, line)
                                end
                            end
                        end
                    end
                end
            end
        end
        for stat_text, mod_data_list in pairs(random_mods) do
            local text = {}
            for _, mod_data in ipairs(mod_data_list) do
                table.insert(text, mod_data.result['mods.stat_text'])
            end
            local tbl = mw.html.create('table')
            tbl
                :attr('class', 'random-modifier-stats mw-collapsed')
                :attr('style', 'text-align: left')
                :tag('tr')
                    :tag('th')
                        :attr('class', 'mw-customtoggle-31')
                        :wikitext(stat_text)
                        :done()
                    :done()
                :tag('tr')
                    :attr('class', 'mw-collapsible mw-collapsed')
                    :attr('id', 'mw-customcollapsible-31')
                    :tag('td')
                        :wikitext(table.concat(text, '<hr style="width: 20%">'))
                        :done()
                    :done()
            table.insert(lines, tostring(tbl))
        end
        return #lines > 0 and table.concat(lines, '<br>') or nil
    end
end

h.proc.text = h.proc.factory.value{cast = m_util.cast.text}
h.proc.boolean = h.proc.factory.value{cast = m_util.cast.boolean}
h.proc.number = h.proc.factory.value{cast = m_util.cast.number}

h.proc.percentage = h.proc.factory.value{
    cast = m_util.cast.number,
    validate = m_util.validate.factory.number_in_range{
        min = 0,
        max = 100,
    },
}

h.proc.size = h.proc.factory.value{
    cast = m_util.cast.number,
    validate = m_util.validate.factory.number_in_range{
        min = 1,
        max = 4,
    },
}

h.proc.character = h.proc.factory.value{
    validate = m_util.validate.factory.string_length{
        min = 1,
        max = 1,
    },
}

h.proc.list = h.proc.factory.list()

--
-- Argument mapping
--
-- [<tpl_args key>] = {
--   inherit  boolean  Whether the item will inherit this key from its base item. Default: true
--   field  string  Cargo field name
--   type  string  Cargo field type
--   func  function  Function to unpack the argument into a native lua value and validate it
--   default  varies  Default value if parameter is not set
--   deprecated  boolean  Set to true for deprecated parameters
-- }
core.map = {
    -- special params
    html = {
        inherit = false,
        field = 'html',
        type = 'Text',
        func = nil,
    },
    infobox_html = {
        inherit = false,
        field = 'infobox_html',
        type = 'Text',
        func = nil,
    },
    metabox_html = {
        inherit = false,
        field = 'metabox_html',
        type = 'Text',
        func = nil,
    },
    implicit_stat_text = {
        inherit = false,
        field = 'implicit_stat_text',
        type = 'Text',
        func = h.proc.factory.stat_text{type='implicit'},
    },
    explicit_stat_text = {
        inherit = false,
        field = 'explicit_stat_text',
        type = 'Text',
        func = h.proc.factory.stat_text{type='explicit'},
    },
    stat_text = {
        inherit = false,
        field = 'stat_text',
        type = 'Text',
        func = function (tpl_args, value)
            if tpl_args.implicit_stat_text or tpl_args.explicit_stat_text then
                local stats = {}
                table.insert(stats, tpl_args.implicit_stat_text) -- No-op if value is nil
                table.insert(stats, tpl_args.explicit_stat_text)
                if tpl_args.is_corrupted then
                    table.insert(stats, m_util.html.poe_color('corrupted', i18n.tooltips.corrupted))
                elseif tpl_args.is_mirrored then
                    table.insert(stats, i18n.tooltips.mirrored)
                elseif tpl_args.is_unmodifiable then
                    table.insert(stats, i18n.tooltips.unmodifiable)
                end
                local sep = string.format('<span class="item-stat-separator -%s"></span>', tpl_args.frame_type)
                value = table.concat(stats, sep)
            end
            return value
        end,
    },
    class_id = {
        inherit = false,
        field = 'class_id',
        type = 'String',
        func = function (tpl_args, value)
            if value == nil then
                error(string.format(i18n.errors.generic_required_parameter, 'class_id'))
            end
            if not m_util.table.has_key(m_game.constants.item.classes, value) or m_game.constants.item.classes[value].disabled then
                error(string.format(i18n.errors.invalid_class_id, tostring(value)))
            end
            return value
        end,
    },
    class = {
        inherit = false,
        field = 'class',
        type = 'String',
        func = function (tpl_args, value)
            local class = m_game.constants.item.classes[tpl_args.class_id].long_upper
            -- Avoids errors with empty item class names later on
            if class == '' then
                class = nil
            end
            return class
        end,
        deprecated = true,
    },
    -- generic
    is_in_game = {
        inherit = false,
        field = 'is_in_game',
        type = 'Boolean',
        func = h.proc.boolean,
        default = true,
    },
    rarity_id = {
        inherit = false,
        field = 'rarity_id',
        type = 'String',
        func = h.proc.factory.value{
            validate = m_util.validate.factory.in_table_keys{
                tbl = m_game.constants.rarities,
                errmsg = i18n.errors.invalid_rarity_id,
            },
        },
    },
    rarity = {
        inherit = false,
        field = 'rarity',
        type = 'String',
        func = function (tpl_args, value)
            return m_game.constants.rarities[tpl_args.rarity_id].long_upper
        end,
        deprecated = true,
    },
    name = {
        inherit = false,
        field = 'name',
        type = 'String',
        func = nil,
    },
    size_x = {
        field = 'size_x',
        type = 'Integer',
        func = h.proc.size,
        default = 1,
    },
    size_y = {
        field = 'size_y',
        type = 'Integer',
        func = h.proc.size,
        default = 1,
    },
    drop_rarities_ids = {
        inherit = false,
        field = 'drop_rarity_ids',
        type = 'List (,) of String',
        func = function (tpl_args, value)
            -- Drop rarities only matter for base items
            if tpl_args._flags.is_derived then
                return nil -- Use default
            end
            return m_util.cast.table(value, {
                callback = m_util.validate.factory.in_table_keys{
                    tbl = m_game.constants.rarities,
                    errmsg = i18n.errors.invalid_rarity_id,
                    errlvl = 4,
                },
            })
        end,
        default = {},
    },
    drop_enabled = {
        inherit = false,
        field = 'drop_enabled',
        type = 'Boolean',
        func = function (tpl_args, value)
            if value == nil then
                return nil -- Use default
            end
            return tpl_args.is_in_game and m_util.cast.boolean(value)
        end,
        default = function (tpl_args)
            return tpl_args.is_in_game
        end,
    },
    drop_level = {
        inherit = false,
        field = 'drop_level',
        type = 'Integer',
        func = h.proc.number,
    },
    drop_level_maximum = {
        inherit = false,
        field = 'drop_level_maximum',
        type = 'Integer',
        func = h.proc.number,
    },
    acquisition_tags = {
        inherit = false,
        field = 'acquisition_tags',
        type = 'List (,) of String',
        func = h.proc.factory.list{
            callback = m_util.validate.factory.in_table_keys{
                tbl = cfg.acquisition_tags,
                errmsg = i18n.errors.invalid_acquisition_tag,
                errlvl = 4,
            },
        },
        default = {},
    },
    drop_areas = {
        inherit = false,
        field = 'drop_areas',
        type = 'List (,) of String',
        func = function (tpl_args, value)
            value = m_util.cast.table(value)
            if type(value) == 'table' and #value > 0 then
                tpl_args._drop_areas_data = m_cargo.array_query{
                    tables = {'areas'},
                    fields = {
                        'areas._pageName=_pageName',
                        'areas.id=id',
                        'areas.name=name',
                        'areas.main_page=main_page',
                        'areas.is_legacy_map_area=is_legacy_map_area'
                    },
                    id_field = 'areas.id',
                    id_array = value,
                    query = {limit=5000},
                }
                if tpl_args._drop_areas_data then
                    tpl_args._legacy_drop_areas = tpl_args._legacy_drop_areas or {}
                    for _, v in ipairs(tpl_args._drop_areas_data) do
                        if m_util.cast.boolean(v.is_legacy_map_area) then
                            tpl_args._flags.has_legacy_drop_areas = true
                            table.insert(tpl_args._legacy_drop_areas, v.id)
                        end
                    end
                end
            end
            return value
        end,
        default = {},
    },
    drop_monsters = {
        inherit = false,
        field = 'drop_monsters',
        type = 'List (,) of Text',
        func = h.proc.list,
        default = {},
    },
    drop_text = {
        inherit = false,
        field = 'drop_text',
        type = 'Text',
        func = h.proc.text,
    },
    required_level = {
        field = 'required_level_base',
        type = 'Integer',
        func = h.proc.number,
        default = 1,
    },
    required_level_final = {
        field = 'required_level',
        type = 'Integer',
        func = function (tpl_args, value)
            value = tpl_args.required_level
            if value < cfg.base_item_required_level_threshold and cfg.base_item_required_level_threshold_classes[tpl_args.class_id] then
                value = 1
            end
            return value
        end,
        default = 1,
    },
    required_dexterity = {
        field = 'required_dexterity',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    required_strength = {
        field = 'required_strength',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    required_intelligence = {
        field = 'required_intelligence',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    inventory_icon = {
        inherit = false,
        field = 'inventory_icon',
        type = 'String',
        func = function (tpl_args, value)
            if not value then
                -- Certain types of items have default inventory icons
                if i18n.default_inventory_icons[tpl_args.class_id] then
                    value = i18n.default_inventory_icons[tpl_args.class_id]
                else
                    for k, v in pairs(tpl_args._flags) do
                        value = v and i18n.default_inventory_icons[k]
                    end
                end
            end
            tpl_args.inventory_icon_id = value or tpl_args.name
            return string.format(i18n.files.inventory_icon, tpl_args.inventory_icon_id) 
        end,
    },
    -- note: this must be called after inventory_icon to work correctly as it depends on tpl_args.inventory_icon_id being set
    alternate_art_inventory_icons = {
        inherit = false,
        field = 'alternate_art_inventory_icons',
        type = 'List (,) of String',
        func = function (tpl_args, value)
            return m_util.cast.table(value, {
                callback = function (value)
                    return string.format(i18n.files.inventory_icon, string.format('%s %s', tpl_args.inventory_icon_id, tostring(value)))
                end,
            })
        end,
        default = {},
    },
    cannot_be_traded_or_modified = {
        inherit = false,
        field = 'cannot_be_traded_or_modified',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    help_text = {
        field = 'help_text',
        type = 'Text',
        func = h.proc.text,
    },
    is_account_bound = {
        inherit = false,
        field = 'is_account_bound',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    flavour_text = {
        inherit = false,
        field = 'flavour_text',
        type = 'Text',
        func = h.proc.factory.value{
            cast = function (value)
                value = m_util.cast.text(value)
                -- Parse harbinger glyphs
                value = string.gsub(value, '<<([Hh][Bb][Gg]%w+)>>', '<span class="glyph %1"></span>')
                return value
            end,
        },
    },
    flavour_text_id = {
        inherit = false,
        field = 'flavour_text_id',
        type = 'String',
        func = nil,
    },
    tags = {
        field = 'tags',
        type = 'List (,) of String',
        func = h.proc.factory.list{
            callback = m_util.validate.factory.in_table_keys{
                tbl = m_game.constants.tags,
                errmsg = i18n.errors.invalid_tag,
                errlvl = 4,
            },
        },
        default = {},
    },
    metadata_id = {
        inherit = false,
        field = 'metadata_id',
        type = 'String',
        func = function (tpl_args, value)
            if value == nil then
                return nil
            end
            -- Unless we're in testing mode, validate that metadata_id is unique
            if not tpl_args.test then
                local results = m_cargo.query(
                    {'items'},
                    {'items._pageName'},
                    {
                        where = string.format(
                            'items.metadata_id = "%s" AND items._pageName != "%s"',
                            value,
                            m_cargo.addslashes(mw.title.getCurrentTitle().prefixedText)
                        )
                    }
                )
                if #results > 0 then
                    error(string.format(i18n.errors.duplicate_metadata, value, results[1]['items._pageName']))
                end
            end
            return value
        end,
    },
    influences = {
        inherit = false,
        field = 'influences',
        type = 'List (,) of String',
        func = h.proc.factory.list{
            callback = m_util.validate.factory.in_table_keys{
                tbl = m_game.constants.influences,
                errmsg = i18n.errors.invalid_influence,
                errlvl = 4,
            },
        },
        default = {},
    },
    is_fractured = {
        inherit = false,
        field = 'is_fractured',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    is_synthesised = {
        inherit = false,
        field = 'is_synthesised',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    is_searing_exarch_item = {
        inherit = false,
        field = 'is_searing_exarch_item',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    is_eater_of_worlds_item = {
        inherit = false,
        field = 'is_eater_of_worlds_item',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    is_veiled = {
        inherit = false,
        field = 'is_veiled',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    is_replica = {
        inherit = false,
        field = 'is_replica',
        type = 'Boolean',
        func = function (tpl_args, value)
            value = m_util.cast.boolean(value)
            if value == true and tpl_args.rarity_id ~= 'unique' then
                error(string.format(i18n.errors.non_unique_flag, 'is_replica'))
            end
            return value
        end,
        default = false,
    },
    is_corrupted = {
        inherit = false,
        field = 'is_corrupted',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    is_mirrored = {
        inherit = false,
        field = nil,
        type = nil,
        func = h.proc.boolean,
        default = false,
    },
    is_unmodifiable = {
        inherit = false,
        field = 'is_unmodifiable',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    is_drop_restricted = {
        inherit = false,
        field = 'is_drop_restricted',
        type = 'Boolean',
        func = h.proc.boolean,
        default = function (tpl_args)
            -- Divination cards have drop restrictions by design.
            if tpl_args.class_id == 'DivinationCard' then
                return true
            end
            for _, key in ipairs({'is_replica', '_drop_areas_data', 'drop_monsters'}) do
                -- arg must be truthy and NOT an empty table
                if tpl_args[key] and not (type(tpl_args[key]) == 'table' and #tpl_args[key] == 0) then
                    return true
                end
            end
            local flags = {
                'is_talisman',
                'is_essence',
                'is_blight_item',
                'is_fossil',
                'is_tattoo',
                'is_delirium_orb',
                'is_catalyst',
            }
            for _, flag in ipairs(flags) do
                if tpl_args._flags[flag] then
                    return true
                end
            end
            return false
        end,
    },
    purchase_costs = {
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            local purchase_costs = {}
            for _, rarity_id in ipairs(m_game.constants.rarity_order) do
                local rtbl = {}
                local prefix = string.format('purchase_cost_%s', rarity_id)
                local i = 1
                while i ~= -1 do
                    local iprefix = prefix .. i
                    local values = {
                        name = tpl_args[iprefix .. '_name'],
                        amount = tonumber(tpl_args[iprefix .. '_amount']),
                        rarity = rarity_id,
                    }
                    if values.name ~= nil and values.amount ~= nil then
                        rtbl[#rtbl+1] = values
                        i = i + 1
                        table.insert(tpl_args._store_data, {
                            _table = 'item_purchase_costs',
                            amount = values.amount,
                            name = values.name,
                            rarity = values.rarity,
                        })
                    else
                        i = -1
                    end
                end
                purchase_costs[rarity_id] = rtbl
            end
            return purchase_costs
        end,
        func_fetch = function (tpl_args)
            if tpl_args.rarity_id ~= 'unique' then
                return
            end
            
            local results = m_cargo.query(
                {'items' ,'item_purchase_costs'},
                {'item_purchase_costs.amount', 'item_purchase_costs.name', 'item_purchase_costs.rarity'},
                {
                    join = 'items._pageID=item_purchase_costs._pageID',
                    where = string.format('items._pageName="%s" AND item_purchase_costs.rarity="unique"', tpl_args.base_item_page),
                }
            )
            
            for _, row in ipairs(results) do
                local values = {
                    rarity = row['item_purchase_costs.rarity'],
                    name = row['item_purchase_costs.name'],
                    amount = tonumber(row['item_purchase_costs.amount']),
                }
                local datavar = tpl_args.purchase_costs[string.lower(values.rarity)]
                datavar[#datavar+1] = values
                table.insert(tpl_args._store_data, {
                    _table = 'item_purchase_costs',
                    amount = values.amount,
                    name = values.name,
                    rarity = values.rarity,
                })
            end
        end,
    },
    is_sellable = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            if value == nil then
                return nil -- Use default
            end
            return tpl_args.is_in_game and m_util.cast.boolean(value)
        end,
        default = function (tpl_args)
            return tpl_args.is_in_game
        end,
    },
    sell_prices_override = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            -- these variables are also used by mods when setting automatic sell prices
            tpl_args.sell_prices = {}
            tpl_args.sell_price_order = {}
            if not tpl_args.is_sellable then
                return nil
            end
            local name
            local amount
            local i = 0
            repeat
                i = i + 1
                name = tpl_args[string.format('sell_price%s_name', i)]
                amount = tpl_args[string.format('sell_price%s_amount', i)]
                
                if name ~= nil and amount ~= nil then
                    tpl_args.sell_price_order[#tpl_args.sell_price_order+1] = name
                    tpl_args.sell_prices[name] = amount
                    table.insert(tpl_args._store_data, {
                        _table = 'item_sell_prices',
                        amount = amount,
                        name = name,
                    })
                end
            until name == nil or amount == nil 
            -- if sell prices are set, the override is active
            for _, _ in pairs(tpl_args.sell_prices) do
                tpl_args._flags.sell_prices_override = true
                break
            end
            return value
        end,
    },
    --
    -- specific section
    --

    -- Most item classes
    quality = {
        inherit = false,
        field = 'quality',
        type = 'Integer',
        -- Must copy to stat for the stat adjustments to work properly
        func = function (tpl_args, value)
            local value = tonumber(value)
            if value then
                core.add_stat(tpl_args, 'quality', value)
                if tpl_args.class_id == 'UtilityFlask' then
                    core.add_stat(tpl_args, 'quality_flask_duration', value)
                elseif tpl_args.class_id == 'Map' then
                    -- quality is added to quantity for maps
                    core.add_stat(tpl_args, 'map_item_drop_quantity_+%', value)
                end
            end
            return value
        end,
        default = 0,
    },
    -- amulets
    is_talisman = {
        field = 'is_talisman',
        type = 'Boolean',
        func = function (tpl_args, value)
            value = m_util.cast.boolean(value)
            tpl_args._flags.is_talisman = value
            return value
        end,
        default = false,
    },
    talisman_tier = {
        field = 'talisman_tier',
        type = 'Integer',
        func = h.proc.number,
    },
    -- flasks
    charges_max = {
        field = 'charges_max',
        type = 'Integer',
        func = h.proc.number,
    },
    charges_per_use = {
        field = 'charges_per_use',
        type = 'Integer',
        func = h.proc.number,
    },
    flask_mana = {
        field = 'mana',
        type = 'Integer',
        func = h.proc.number,
    },
    flask_life = {
        field = 'life',
        type = 'Integer',
        func = h.proc.number,
    },
    flask_duration = {
        field = 'duration',
        type = 'Float',
        func = h.proc.number,
    },
    buff_id = {
        field = 'id',
        type = 'String',
        func = nil,
    },
    buff_values = {
        field = 'buff_values',
        type = 'List (,) of Integer',
        func = function (tpl_args, value)
            local values = {}
            local i = 0
            repeat 
                i = i + 1
                local key = 'buff_value' .. i
                values[i] = tonumber(tpl_args[key])
                tpl_args[key] = nil
            until values[i] == nil
            
            -- needed so the values copyied from unique item base isn't overriden
            if #values >= 1 then
                value = values
            end
            return value
        end,
        func_copy = function (tpl_args, value)
            tpl_args.buff_values = m_util.string.split(value, ',%s*')
        end,
        default = {},
    },
    buff_stat_text = {
        field = 'stat_text',
        type = 'String',
        func = nil,
    },
    buff_icon = {
        field = 'icon',
        type = 'String',
        func = function (tpl_args, value)
            return string.format(i18n.files.status_icon, tpl_args.name)
        end,
    },
    
    -- weapons
    critical_strike_chance = {
        field = 'critical_strike_chance',
        type = 'Float',
        func = h.proc.number,
    },
    attack_speed = {
        field = 'attack_speed',
        type = 'Float',
        func = h.proc.number,
    },
    weapon_range = {
        field = 'weapon_range',
        type = 'Float',
        func = h.proc.number,
    },
    physical_damage_min = {
        field = 'physical_damage_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    physical_damage_max = {
        field = 'physical_damage_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    fire_damage_min = {
        field = 'fire_damage_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    fire_damage_max = {
        field = 'fire_damage_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    cold_damage_min = {
        field = 'cold_damage_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    cold_damage_max = {
        field = 'cold_damage_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    lightning_damage_min = {
        field = 'lightning_damage_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    lightning_damage_max = {
        field = 'lightning_damage_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    chaos_damage_min = {
        field = 'chaos_damage_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    chaos_damage_max = {
        field = 'chaos_damage_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    -- armor-type stuff
    armour = {
        inherit = false,
        field = 'armour',
        type = nil,
        func = nil,
    },
    armour_min = {
        field = 'armour_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    armour_max = {
        field = 'armour_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    evasion = {
        inherit = false,
        field = 'evasion',
        type = nil,
        func = nil,
    },
    evasion_min = {
        field = 'evasion_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    evasion_max = {
        field = 'evasion_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    energy_shield = {
        inherit = false,
        field = 'energy_shield',
        type = nil,
        func = nil,
    },
    energy_shield_min = {
        field = 'energy_shield_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    energy_shield_max = {
        field = 'energy_shield_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    ward = {
        inherit = false,
        field = 'ward',
        type = nil,
        func = nil,
    },
    ward_min = {
        field = 'ward_min',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    ward_max = {
        field = 'ward_max',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    -- This is the inherent penality from the armour piece if any
    movement_speed = {
        field = 'movement_speed',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    -- shields
    block = {
        field = 'block',
        type = 'Integer',
        func = h.proc.number,
    },
    -- skill gem stuff
    gem_description = {
        field = 'gem_description',
        type = 'Text',
        func = h.proc.text,
    },
    dexterity_percent = {
        field = 'dexterity_percent',
        type = 'Integer',
        func = h.proc.percentage,
    },
    strength_percent = {
        field = 'strength_percent',
        type = 'Integer',
        func = h.proc.percentage,
    },
    intelligence_percent = {
        field = 'intelligence_percent',
        type = 'Integer',
        func = h.proc.percentage,
    },
    primary_attribute = {
        field = 'primary_attribute',
        type = 'String',
        func = function (tpl_args, value)
            for _, attr in ipairs(m_game.constants.attribute_order) do
                local val = tpl_args[attr .. '_percent'] 
                if val and val >= 60 then
                    return attr
                end
            end
            return 'none'
        end,
    },
    gem_tags = {
        field = 'gem_tags',
        type = 'List (,) of String',
        func = h.proc.factory.list{
            callback = m_util.validate.factory.in_table_keys{
                tbl = m_game.constants.item.gem_tags_lookup,
                errmsg = i18n.errors.invalid_gem_tag,
                errlvl = 4,
            },
        },
        default = {},
    },
    is_vaal_skill_gem = {
        field = 'is_vaal_skill_gem',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    vaal_variant_id = {
        inherit = true,
        field = 'vaal_variant_id',
        type = 'String',
        func = nil,
    },
    -- Support gems only
    support_gem_letter = {
        field = 'support_gem_letter',
        type = 'String',
        func = h.proc.character,
    },
    support_gem_letter_html = {
        field = 'support_gem_letter_html',
        type = 'Text',
        func = function (tpl_args, value)
            if tpl_args.support_gem_letter == nil then
                return nil
            end
            for k, v in pairs(m_game.constants.attributes) do
                local key = string.format('%s_percent', v.long_lower)
                if tpl_args[key] and tpl_args[key] > 50 then
                    value = tostring(
                        mw.html.create('span')
                            :attr('class', string.format('support-gem-id-%s', v.color))
                            :wikitext(tpl_args.support_gem_letter)
                    )
                    break
                end
            end
            return value
        end,
    },
    is_awakened_support_gem = {
        field = 'is_awakened_support_gem',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    awakened_variant_id = {
        field = 'awakened_variant_id',
        type = 'String',
        func = nil,
    },
    --
    -- Jewels
    --
    jewel_limit = {
        inherit = false,
        field = 'jewel_limit',
        type = 'String',
        func = nil,
    },
    --
    -- Maps
    --
    map_tier = {
        field = 'tier',
        type = 'Integer',
        func = h.proc.number,
    },
    map_guild_character = {
        field = 'guild_character',
        type = 'String',
        func = h.proc.character,
    },
    map_area_id = {
        field = 'area_id',
        type = 'String',
        func = nil, -- TODO: Validate against a query?
    },
    map_area_level = {
        field = 'area_level',
        type = 'Integer',
        func = h.proc.number,
    },
    unique_map_guild_character = {
        field = 'unique_guild_character',
        type = 'String',
        func = h.proc.character,
        func_copy = function (tpl_args, value)
            tpl_args.map_guild_character = value
        end,
    },
    unique_map_area_id = {
        field = 'unique_area_id',
        type = 'String',
        func = nil, -- TODO: Validate against a query?
        func_copy = function (tpl_args, value)
            tpl_args.map_area_id = value
        end,
    },
    unique_map_area_level = {
        field = 'unique_area_level',
        type = 'Integer',
        func = h.proc.number,
        func_copy = function (tpl_args, value)
            tpl_args.map_area_level = value
        end,
    },
    map_series = {
        field = 'series',
        type = 'String',
        func = function (tpl_args, value)
            if tpl_args.rarity == 'normal' and value == nil then
                error(string.format(i18n.errors.generic_required_parameter, 'map_series'))
            end
            return value
        end,
    },
    -- atlas info is only for the current map series
    atlas_x = {
        inherit = false,
        field = 'x',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y = {
        inherit = false,
        field = 'y',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_region_id = {
        inherit = false,
        field = 'region_id',
        type = 'String',
        func = nil,
    },
    atlas_region_minimum = {
        inherit = false,
        field = 'region_minimum',
        type = 'Integer',
        func = h.proc.number,
    },
    atlas_x0 = {
        inherit = false,
        field = 'x0',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_x1 = {
        inherit = false,
        field = 'x1',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_x2 = {
        inherit = false,
        field = 'x2',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_x3 = {
        inherit = false,
        field = 'x3',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_x4 = {
        inherit = false,
        field = 'x4',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y0 = {
        inherit = false,
        field = 'y0',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y1 = {
        inherit = false,
        field = 'y1',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y2 = {
        inherit = false,
        field = 'y2',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y3 = {
        inherit = false,
        field = 'y3',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y4 = {
        inherit = false,
        field = 'y4',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_map_tier0 = {
        field = 'map_tier0',
        type = 'Integer',
        func = h.proc.number,
    },
    atlas_map_tier1 = {
        field = 'map_tier1',
        type = 'Integer',
        func = h.proc.number,
    },
    atlas_map_tier2 = {
        field = 'map_tier2',
        type = 'Integer',
        func = h.proc.number,
    },
    atlas_map_tier3 = {
        field = 'map_tier3',
        type = 'Integer',
        func = h.proc.number,
    },
    atlas_map_tier4 = {
        field = 'map_tier4',
        type = 'Integer',
        func = h.proc.number,
    },
    atlas_connections = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            value = {}
            local cont = true
            local i = 1
            while cont do
                local prefix = string.format('atlas_connection%s_', i)
                local regions = tpl_args[prefix .. 'tier']
                local data = {
                    _table = 'atlas_connections',
                    map1 = string.format('%s (%s)', tpl_args.name, tpl_args.map_series or ''),
                    map2 = tpl_args[prefix .. 'target'],
                }
                
                if regions and data.map2 then
                    regions = m_util.string.split(regions, ',%s*')
                    if #regions ~= 5 then
                        error(string.format(i18n.errors.invalid_region_upgrade_count, i, #regions))
                    end
                    for index, value in ipairs(regions) do
                        data['region' .. (index - 1)] = m_util.cast.boolean(value)
                    end
                    
                    value[data.map2] = data
                    table.insert(tpl_args._store_data, data)
                else
                    cont = false
                    if i == 1 then
                        value = nil
                    end
                end
                i = i + 1
            end
            return value
        end,
    },
    --
    -- Map fragments
    --
    is_scarab = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_scarab = m_util.table.contains(tpl_args.tags, 'scarab')
            return value
        end,
    },
    --
    -- Stackable items
    --
    stack_size = {
        inherit = false,
        field = 'stack_size',
        type = 'Integer',
        func = h.proc.number,
    },
    stack_size_currency_tab = {
        inherit = false,
        field = 'stack_size_currency_tab',
        type = 'Integer',
        func = h.proc.number,
    },
    description = {
        inherit = false,
        field = 'description',
        type = 'Text',
        func = h.proc.text,
    },
    -- Essences
    is_essence = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            value = m_util.cast.boolean(value)
            tpl_args._flags.is_essence = value
            return value
        end,
        default = false,
    },
    essence_level_restriction = {
        inherit = false,
        field = 'level_restriction',
        type = 'Integer',
        func = h.proc.number,
    },
    essence_level = {
        inherit = false,
        field = 'level',
        type = 'Integer',
        func = h.proc.number,
    },
    essence_type = {
        inherit = false,
        field = 'type',
        type = 'Integer',
        func = h.proc.number,
    },
    essence_category = {
        inherit = false,
        field = 'category',
        type = 'String',
        func = nil,
    },
    -- Oils
    is_blight_item = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_blight_item = tpl_args.blight_item_tier ~= nil
            return value
        end,
    },
    blight_item_tier = {
        inherit = false,
        field = 'tier',
        type = 'Integer',
        func = h.proc.number,
    },
    -- Fossils
    is_fossil = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_fossil = tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'CurrencyDelve', 1, true) ~= nil
            return value
        end,
    },
    -- Tattoos
    is_tattoo = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_tattoo = tpl_args.tattoo_target ~= nil
            return value
        end,
    },
    tattoo_target = {
        inherit = false,
        field = 'target',
        type = 'String',
        func = nil,
    },
    tattoo_tribe = {
        inherit = false,
        field = 'tribe',
        type = 'Integer',
        func = h.proc.number,
    },
    tattoo_limit = {
        inherit = false,
        field = 'tattoo_limit',
        type = 'String',
        func = nil,
    },
    tattoo_min_adjacent = {
        inherit = false,
        field = 'min_adjacent',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    tattoo_max_adjacent = {
        inherit = false,
        field = 'max_adjacent',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    tattoo_skill_id = {
        inherit = false,
        field = 'skill_id',
        type = 'String',
        func = nil,
    },
    -- Delirium orbs
    is_delirium_orb = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_delirium_orb = tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'CurrencyAfflictionOrb', 1, true) ~= nil
            return value
        end,
    },
    -- Catalysts
    is_catalyst = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_catalyst = m_util.table.contains(tpl_args.tags, 'catalyst')
            return value
        end,
    },
    -- Cosmetic items
    cosmetic_type = {
        inherit = false,
        field = 'cosmetic_type',
        type = 'String',
        func = h.proc.factory.value{
            validate = m_util.validate.factory.in_table_keys{
                tbl = m_game.constants.item.cosmetic_item_types,
                errmsg = i18n.errors.invalid_cosmetic_type,
            },
        },
    },
    cosmetic_theme = {
        inherit = false,
        field = 'theme',
        type = 'String',
        func = nil,
    },
    cosmetic_target = {
        inherit = false,
        field = 'target',
        type = 'List (,) of String',
        func = h.proc.list,
        default = {},
    },
    --
    -- Harvest seeds
    --
    seed_type_id = {
        inherit = false,
        field = 'type_id',
        type = 'String',
        func = nil,
    },
    seed_type = {
        inherit = false,
        field = 'type',
        type = 'String',
        func = function (tpl_args, value)
            if tpl_args.seed_type_id ~= 'none' or tpl_args.seed_type_id ~= nil then
                value = m_game.seed_types[tpl_args.seed_type_id]
            end
            return value
        end,
    },
    seed_type_html = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            if tpl_args.seed_type ~= nil then
                value = m_util.html.poe_color(tpl_args.seed_type_id, tpl_args.seed_type)
            end
            return value
        end,
    },
    seed_effect = {
        inherit = false,
        field = 'effect',
        type = 'Text',
        func = nil,
    },
    seed_tier = {
        inherit = false,
        field = 'tier',
        type = 'Integer',
        func = h.proc.number,
    },
    seed_growth_cycles = {
        inherit = false,
        field = 'growth_cycles',
        type = 'Integer',
        func = h.proc.number,
    },
    seed_required_nearby_seed_tier = {
        inherit = false,
        field = 'required_nearby_seed_tier',
        type = 'Integer',
        func = h.proc.number,
    },
    seed_required_nearby_seed_amount = {
        inherit = false,
        field = 'required_nearby_seed_amount',
        type = 'Integer',
        func = h.proc.number,
    },
    seed_consumed_wild_lifeforce_percentage = {
        inherit = false,
        field = 'consumed_wild_lifeforce_percentage',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    seed_consumed_vivid_lifeforce_percentage = {
        inherit = false,
        field = 'consumed_vivid_lifeforce_percentage',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    seed_consumed_primal_lifeforce_percentage = {
        inherit = false,
        field = 'consumed_primal_lifeforce_percentage',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    seed_granted_craft_option_ids = {
        inherit = false,
        field = 'granted_craft_option_ids',
        type = 'List (,) of String',
        func = h.proc.list,
        default = {},
    },
    --
    -- Harvest planet boosters
    --
    plant_booster_radius = {
        inherit = false,
        field = 'radius',
        type = 'Integer',
        func = h.proc.number,
    },
    plant_booster_lifeforce = {
        inherit = false,
        field = 'lifeforce',
        type = 'Integer',
        func = h.proc.number,
    },
    plant_booster_additional_crafting_options = {
        inherit = false,
        field = 'additional_crafting_options',
        type = 'Integer',
        func = h.proc.number,
    },
    plant_booster_extra_chances = {
        inherit = false,
        field = 'extra_chances',
        type = 'Integer',
        func = h.proc.number,
    },
    --
    -- Heist properties
    --
    heist_required_job_id = {
        field = 'required_job_id',
        type = 'String',
        func = h.proc.text,
    },
    heist_required_job_level = {
        field = 'required_job_level',
        type = 'Integer',
        func = h.proc.number,
    },
    heist_data = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            if tpl_args.heist_required_job_level then
                if tpl_args.heist_required_job_id then
                    local results = m_cargo.query(
                        {'heist_npc_skills', 'heist_jobs', 'heist_npcs'},
                        {'heist_npcs.name', 'heist_jobs.name'},
                        {
                            join = 'heist_npc_skills.job_id=heist_jobs.id, heist_npc_skills.npc_id=heist_npcs.id',
                            where = string.format('heist_npc_skills.job_id = "%s" AND heist_npc_skills.level >= %s', tpl_args.heist_required_job_id, tpl_args.heist_required_job_level),
                        }
                    )
                    local npcs = {}
                    for _, row in ipairs(results) do
                        npcs[#npcs+1] = row['heist_npcs.name']
                    end
                    tpl_args.heist_required_npcs = table.concat(npcs, ', ')
                    tpl_args.heist_required_job = results[1]['heist_jobs.name']
                else
                    tpl_args.heist_required_job = i18n.tooltips.heist_any_job
                end
            end
            return value
        end,
    },
    --
    -- Hideout decorations
    --
    is_master_doodad = {
        inherit = false,
        field = 'is_master_doodad',
        type = 'Boolean',
        func = h.proc.boolean,
        default = false,
    },
    variation_count = {
        inherit = false,
        field = 'variation_count',
        type = 'Integer',
        func = h.proc.number,
    },
    --
    -- Prophecies
    --
    is_prophecy = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, value)
            tpl_args._flags.is_prophecy = tpl_args.prophecy_id ~= nil
            return value
        end,
        default = false,
    },
    prophecy_id = {
        inherit = false,
        field = 'prophecy_id',
        type = 'String',
        func = nil,
    },
    prediction_text = {
        inherit = false,
        field = 'prediction_text',
        type = 'Text',
        func = h.proc.text,
    },
    seal_cost = {
        inherit = false,
        field = 'seal_cost',
        type = 'Integer',
        func = h.proc.number,
    },
    prophecy_reward = {
        inherit = false,
        field = 'reward',
        type = 'Text',
        func = h.proc.text,
    },
    prophecy_objective = {
        inherit = false,
        field = 'objective',
        type = 'Text',
        func = h.proc.text,
    },
    --
    -- Divination cards
    --
    card_art = {
        inherit = false,
        field = 'card_art',
        type = 'Page',
        func = function (tpl_args, value)
            return string.format(i18n.files.divination_card_art, value or tpl_args.name)
        end,
    },
    card_background = {
        inherit = false,
        field = 'card_background',
        type = 'List (,) of Integer',
        func = h.proc.factory.list{
            callback = m_util.cast.number,
        },
        default = {},
    },
    --
    -- Sentinels
    --
    sentinel_duration = {
        field = 'duration',
        type = 'Integer',
        func = h.proc.number,
    },
    sentinel_empowers = {
        field = 'empowers',
        type = 'Integer',
        func = h.proc.number,
    },
    sentinel_empowerment = {
        field = 'empowerment',
        type = 'Integer',
        func = h.proc.number,
    },
    sentinel_charge = {
        field = 'charge',
        type = 'Integer',
        func = h.proc.number,
    },
    sentinel_monster = {
        inherit = false,
        field = 'monster',
        type = 'String',
        func = nil,
    },
    sentinel_monster_level = {
        inherit = false,
        field = 'monster_level',
        type = 'Integer',
        func = h.proc.number,
    },
    --
    -- Corpse items
    --
    corpse_tier = {
        inherit = false,
        field = 'tier',
        type = 'Integer',
        func = function (tpl_args, value)
            if tpl_args.metadata_id ~= nil then
                for k, v in ipairs({'Low', 'Mid', 'High'}) do
                    if string.find(tpl_args.metadata_id, 'Metadata/Items/ItemisedCorpses/%w+' .. v) then
                        return k
                    end
                end
            end
            return nil
        end,
    },
    monster_category = {
        inherit = false,
        field = 'monster_category',
        type = 'String',
        func = h.proc.factory.value{
            validate = m_util.validate.factory.in_table_keys{
                tbl = m_game.constants.monster.categories,
                errmsg = i18n.errors.invalid_monster_category,
            },
        },
    },
    monster_category_html = {
        inherit = false,
        field = 'monster_category_html',
        type = 'Text',
        func = function (tpl_args, value)
            if tpl_args.monster_category ~= nil then
                value = mw.html.create()
                    :tag('span')
                        :addClass('mon-cat -' .. m_game.constants.monster.categories[tpl_args.monster_category].long_lower)
                        :done()
                    :wikitext(m_game.constants.monster.categories[tpl_args.monster_category].long_upper)
                value = m_util.html.poe_color('value', tostring(value))
            end
            return value
        end,
    },
    monster_abilities = {
        inherit = false,
        field = 'monster_abilities',
        type = 'Text',
        func = nil,
    },
    --
    -- Embers of the Allflame
    --
    pack_id = {
        inherit = false,
        field = 'pack_id',
        type = 'String',
        func = nil,
    },
    pack_size = {
        inherit = false,
        field = 'pack_size',
        type = nil,
        func = nil,
    },
    pack_min_size = {
        inherit = false,
        field = 'pack_min_size',
        type = 'Integer',
        func = h.proc.number,
        default = 1,
    },
    pack_max_size = {
        inherit = false,
        field = 'pack_max_size',
        type = 'Integer',
        func = h.proc.number,
        default = 1,
    },
    pack_leader_chance = {
        inherit = false,
        field = 'pack_leader_chance',
        type = 'Float',
        func = h.proc.number,
    },

    -- ------------------------------------------------------------------------
    -- derived stats
    -- ------------------------------------------------------------------------
    
    -- Populated by processing base item
    base_item_id = {
        inherit = false,
        field = 'base_item_id',
        type = 'String',
        func = nil,
    },
    base_item_page = {
        inherit = false,
        field = 'base_item_page',
        type = 'Page',
        func = nil,
    },
    base_item = {
        inherit = false,
        field = 'base_item',
        type = 'String',
        func = nil,
    },
    name_list = {
        inherit = false,
        field = 'name_list',
        type = 'List (�) of String',
        func = function (tpl_args, value)
            value = m_util.cast.table(value)
            value[#value+1] = tpl_args.name
            return value
        end,
        default = {},
    },
    frame_type = {
        inherit = false,
        field = 'frame_type',
        type = 'String',
        func = function (tpl_args, value)
            if value then
                return value
            end
            if tpl_args._flags.is_prophecy then
                return 'prophecy'
            end
            local var = cfg.class_specifics[tpl_args.class_id]
            if var ~= nil and var.frame_type ~= nil then
                return var.frame_type
            end
            return tpl_args.rarity_id
        end,
    },
    --
    -- args populated by mod validation
    --
    physical_damage_html = {
        inherit = false,
        field = 'physical_damage_html',
        type = 'Text',
        func = h.proc.factory.damage_html{type = 'physical'},
    },
    fire_damage_html = {
        inherit = false,
        field = 'fire_damage_html',
        type = 'Text',
        func = h.proc.factory.damage_html{type = 'fire'},
    },
    cold_damage_html = {
        inherit = false,
        field = 'cold_damage_html',
        type = 'Text',
        func = h.proc.factory.damage_html{type = 'cold'},
    },
    lightning_damage_html = {
        inherit = false,
        field = 'lightning_damage_html',
        type = 'Text',
        func = h.proc.factory.damage_html{type = 'lightning'},
    },
    chaos_damage_html = {
        inherit = false,
        field = 'chaos_damage_html',
        type = 'Text',
        func = h.proc.factory.damage_html{type = 'chaos'},
    },
    damage_avg = {
        inherit = false,
        field = 'damage_avg',
        type = 'Text',
        func = function (tpl_args, value)
            local min = 0
            local max = 0
            for _, damage_type in ipairs(m_game.constants.damage_type_order) do
                min = min + tpl_args[damage_type .. '_damage_min_range_average']
                max = max + tpl_args[damage_type .. '_damage_max_range_average']
            end
            value = (min + max) / 2
            return value
        end,
    },
    damage_html = {
        inherit = false,
        field = 'damage_html',
        type = 'Text',
        func = function (tpl_args, value)
            local text = {}
            for _, dkey in ipairs(m_game.constants.damage_type_order) do
                local range = tpl_args[dkey .. '_damage_html']
                if range ~= nil then
                    text[#text+1] = range
                end
            end
            if #text > 0 then
                value = table.concat(text, '<br>')
            end
            return value
        end,
    },
    jewel_radius_html = {
        inherit = false,
        field = 'radius_html',
        type = 'Text',
        func = function (tpl_args, value)
            -- Get radius from stats
            local radius = tpl_args._stats.local_jewel_effect_base_radius
            if radius then
                radius = radius.min
                local size = m_game.constants.item.jewel_radius_to_size[radius] or radius
                local color =  radius == 0 and 'mod' or 'value'
                value = m_util.html.poe_color(color, size)
            end
            return value
        end,
    },
    drop_areas_html = {
        inherit = false,
        field = 'drop_areas_html',
        type = 'Text',
        func = function (tpl_args, value)
            if tpl_args._drop_areas_data == nil then
                return value
            end
            if value ~= nil then
                return value
            end
            local areas = {}
            for _, data in pairs(tpl_args._drop_areas_data) do
                -- skip legacy maps in the drop html listing
                if not string.match(data.id, '^Map.+') or string.match(data.id, '^MapWorlds.+') or string.match(data.id, '^MapAtziri.+') then
                    areas[#areas+1] = string.format('[[%s|%s]]', data.main_page or data._pageName, data.main_page or data.name)
                end
            end
            return table.concat(areas, ' • ')
        end,
    },
    release_version = {
        inherit = false,
        field = 'release_version',
        type = 'String',
        func = nil,
    },
    removal_version = {
        inherit = false,
        field = 'removal_version',
        type = 'String',
        func = nil,
    },
    --
    -- args governing use of the template itself
    -- 
    suppress_improper_modifiers_category = {
        inherit = false,
        field = nil,
        func = h.proc.boolean,
        default = false,
    },
    disable_automatic_recipes = {
        inherit = false,
        field = nil,
        func = h.proc.boolean,
        default = false,
    },
}

core.stat_map = {
    required_level_final = {
        field = 'required_level',
        stats_add = {
            'local_level_requirement_+',
        },
        minimum = 1,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    weapon_range = {
        field = 'weapon_range',
        stats_add_distance = {
            'local_weapon_range_+',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%.1f',
        },
    },
    physical_damage_min = {
        field = 'physical_damage_min',
        stats_add = {
            'local_minimum_added_physical_damage',
        },
        stats_increased = {
            'local_physical_damage_+%',
            'quality',
        },
        stats_override = {
            ['local_weapon_no_physical_damage'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    physical_damage_max = {
        field = 'physical_damage_max',
        stats_add = {
            'local_maximum_added_physical_damage',
        },
        stats_increased = {
            'local_physical_damage_+%',
            'quality',
        },
        stats_override = {
            ['local_weapon_no_physical_damage'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    fire_damage_min = {
        field = 'fire_damage_min',
        stats_add = {
            'local_minimum_added_fire_damage',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
            color = 'fire',
        },
    },
    fire_damage_max = {
        field = 'fire_damage_max',
        stats_add = {
            'local_maximum_added_fire_damage',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
            color = 'fire',
        },
    },
    cold_damage_min = {
        field = 'cold_damage_min',
        stats_add = {
            'local_minimum_added_cold_damage',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
            color = 'cold',
        },
    },
    cold_damage_max = {
        field = 'cold_damage_max',
        stats_add = {
            'local_maximum_added_cold_damage',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
            color = 'cold',
        },
    },
    lightning_damage_min = {
        field = 'lightning_damage_min',
        stats_add = {
            'local_minimum_added_lightning_damage',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
            color = 'lightning',
        },
    },
    lightning_damage_max = {
        field = 'lightning_damage_max',
        stats_add = {
            'local_maximum_added_lightning_damage',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
            color = 'lightning',
        },
    },
    chaos_damage_min = {
        field = 'chaos_damage_min',
        stats_add = {
            'local_minimum_added_chaos_damage',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
            color = 'chaos',
        },
    },
    chaos_damage_max = {
        field = 'chaos_damage_max',
        stats_add = {
            'local_maximum_added_chaos_damage',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
            color = 'chaos',
        },
    },
    critical_strike_chance = {
        field = 'critical_strike_chance',
        stats_add = {
            'local_critical_strike_chance',
        },
        stats_increased = {
            'local_critical_strike_chance_+%',
        },
        stats_override = {
            ['local_weapon_crit_chance_is_100'] = {min=100, max=100},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%.2f',
            inline = '%s%%',
        },
    },
    attack_speed = {
        field = 'attack_speed',
        stats_increased = {
            'local_attack_speed_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%.2f',
        },
    },
    flask_life = {
        field = 'life',
        stats_add = {
            'local_flask_life_to_recover',
        },
        stats_increased = {
            'local_flask_life_to_recover_+%',
            'local_flask_amount_to_recover_+%',
            'quality',
        },
        html_fmt_options = {
            fmt = '%i',
        },
    },
    flask_mana = {
        field = 'mana',
        stats_add = {
            'local_flask_mana_to_recover',
        },
        stats_increased = {
            'local_flask_mana_to_recover_+%',
            'local_flask_amount_to_recover_+%',
            'quality',
        },
        html_fmt_options = {
            fmt = '%i',
        },
    },
    flask_duration = {
        field = 'duration',
        stats_increased = {
            'local_flask_duration_+%',
            -- regular quality isn't used here because it doesn't increase duration of life/mana/hybrid flasks
            'quality_flask_duration',
        },
        stats_increased_inverse = {
            'local_flask_recovery_speed_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%.2f',
        },
    },
    charges_per_use = {
        field = 'charges_per_use',
        stats_increased = {
            'local_charges_used_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    charges_max = {
        field = 'charges_max',
        stats_add = {
            'local_extra_max_charges',
        },
        stats_increased = {
            'local_max_charges_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    armour = {
        arg = {
            min = 'armour_min',
            max = 'armour_max',
        },
        field = 'armour',
        stats_add = {
            'local_base_physical_damage_reduction_rating',
        },
        stats_increased = {
            'local_physical_damage_reduction_rating_+%',
            'local_armour_and_energy_shield_+%',
            'local_armour_and_evasion_+%',
            'local_armour_and_evasion_and_energy_shield_+%',
            'quality',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    evasion = {
        arg = {
            min = 'evasion_min',
            max = 'evasion_max',
        },
        field = 'evasion',
        stats_add = {
            'local_base_evasion_rating',
            'local_evasion_rating_and_energy_shield',
        },
        stats_increased = {
            'local_evasion_rating_+%',
            'local_evasion_and_energy_shield_+%',
            'local_armour_and_evasion_+%',
            'local_armour_and_evasion_and_energy_shield_+%',
            'quality',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    energy_shield = {
        arg = {
            min = 'energy_shield_min',
            max = 'energy_shield_max',
        },
        field = 'energy_shield',
        stats_add = {
            'local_energy_shield',
            'local_evasion_rating_and_energy_shield',
        },
        stats_increased = {
            'local_energy_shield_+%',
            'local_armour_and_energy_shield_+%',
            'local_evasion_and_energy_shield_+%',
            'local_armour_and_evasion_and_energy_shield_+%',
            'quality',
        },
        stats_override = {
            ['local_no_energy_shield'] = {min=0, max=0},
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    ward = {
        arg = {
            min = 'ward_min',
            max = 'ward_max',
        },
        field = 'ward',
        stats_add = {
            'local_ward',
        },
        stats_increased = {
            'local_ward_+%',
            'quality',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    block = {
        field = 'block',
        stats_add = {
            'local_additional_block_chance_%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
            inline = '%s%%',
        },
    },
    required_dexterity = {
        field = 'required_dexterity',
        stats_add = {
            'local_dexterity_requirement_+'
        },
        stats_increased = {
            'local_dexterity_requirement_+%',
            'local_attribute_requirements_+%',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
            ['local_no_attribute_requirements'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    required_intelligence = {
        field = 'required_intelligence',
        stats_add = {
            'local_intelligence_requirement_+'
        },
        stats_increased = {
            'local_intelligence_requirement_+%',
            'local_attribute_requirements_+%',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
            ['local_no_attribute_requirements'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    required_strength = {
        field = 'required_strength',
        stats_add = {
            'local_strength_requirement_+'
        },
        stats_increased = {
            'local_strength_requirement_+%',
            'local_attribute_requirements_+%',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
            ['local_no_attribute_requirements'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    map_area_level = {
        field = 'map_area_level',
        stats_override = {
            ['map_item_level_override'] = true,
        },
    },
    sentinel_duration = {
        field = 'duration',
        stats_add = {
            'local_sentinel_duration_+',
        },
        stats_increased = {
            'local_sentinel_drone_duration_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    sentinel_empowers = {
        field = 'empowers',
        stats_add = {
            'local_sentinel_tag_limit_+',
        },
        stats_increased = {
            'local_sentinel_tag_limit_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    sentinel_empowerment = {
        field = 'empowerment',
        stats_add = {
            'local_sentinel_drone_difficulty_+',
        },
        stats_increased = {
            'local_sentinel_drone_difficulty_+%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    sentinel_charge = {
        field = 'charge',
        stats_add = {
            'local_sentinel_drone_charge_+',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    pack_size = {
        arg = {
            min = 'pack_min_size',
            max = 'pack_max_size',
        },
        field = 'pack_size',
        minimum = 1,
        html_fmt_options = {
            fmt = '%i',
            fmt_range = '%s-%s',
        },
    },
}

core.dps_map = {
    physical_dps = {
        field = 'physical_dps',
        damage_args = {'physical_damage'},
        label_infobox = i18n.tooltips.physical_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'value',
        },
    },
    fire_dps = {
        field = 'fire_dps',
        damage_args = {'fire_damage'},
        label_infobox = i18n.tooltips.fire_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'fire',
        },
    },
    cold_dps = {
        field = 'cold_dps',
        damage_args = {'cold_damage'},
        label_infobox = i18n.tooltips.cold_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'cold',
        },
    },
    lightning_dps = {
        field = 'lightning_dps',
        damage_args = {'lightning_damage'},
        label_infobox = i18n.tooltips.lightning_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'lightning',
        },
    },
    chaos_dps = {
        field = 'chaos_dps',
        damage_args = {'chaos_damage'},
        label_infobox = i18n.tooltips.chaos_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'chaos',
        },
    },
    elemental_dps = {
        field = 'elemental_dps',
        damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'},
        label_infobox = i18n.tooltips.elemental_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'value',
        },
    },
    poison_dps = {
        field = 'poison_dps',
        damage_args = {'physical_damage', 'chaos_damage'},
        label_infobox = i18n.tooltips.poison_dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'value',
        },
    },
    dps = {
        field = 'dps',
        damage_args = {'physical_damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'},
        label_infobox = i18n.tooltips.dps,
        html_fmt_options = {
            fmt = '%.1f',
            color = 'value',
        },
    },
}

return core