Path of Exile Wiki

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

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

READ MORE

Path of Exile Wiki
Register
(Created page with "------------------------------------------------------------------------------- -- -- Core confirguation and functions for Module:Item2 and submodules -- -------------------...")
 
No edit summary
(28 intermediate revisions by the same user not shown)
Line 9: Line 9:
   
 
local m_game = mw.loadData('Module:Game')
 
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')
   
 
-- 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 20: Line 23:
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
   
  +
local h = {}
  +
  +
-- ----------------------------------------------------------------------------
  +
-- 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. 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
  +
-- 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
  +
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 value = tpl_args._stats[data.key]
  +
if value ~= nil then
  +
base_values[i] = value.min
  +
temp_values[#temp_values+1] = {value=value, index=i}
  +
end
  +
end
  +
else
  +
-- Regular. Look for key exactly as written in tpl_args
  +
for i, data in ipairs(args.parts) do
  +
base_values[i] = tpl_args[data.key]
  +
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
  +
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, frame, 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 or opt.hide_default == v.max then
  +
hide = true
  +
end
  +
else
  +
local v = data.value
  +
if opt.hide_default == v.min or 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, frame, 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.stats_update(tpl_args, id, value, modid, key)
  +
if tpl_args[key][id] == nil then
  +
tpl_args[key][id] = {
  +
references = {modid},
  +
min = value.min,
  +
max = value.max,
  +
avg = value.avg,
  +
}
  +
else
  +
if modid ~= nil then
  +
table.insert(tpl_args[key][id].references, modid)
  +
end
  +
tpl_args[key][id].min = tpl_args[key][id].min + value.min
  +
tpl_args[key][id].max = tpl_args[key][id].max + value.max
  +
tpl_args[key][id].avg = tpl_args[key][id].avg + value.avg
  +
end
  +
end
  +
  +
--
  +
-- Functions for processing tpl_args
  +
--
  +
h.proc = {}
  +
h.proc.factory = {}
  +
  +
function h.proc.factory.value(args)
  +
args = args or {}
  +
return function (tpl_args, frame, 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, frame, 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, frame, 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, frame)
  +
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
  +
  +
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.list = h.proc.factory.list()
  +
  +
-- Process mod stats
 
function h.process_mod_stats(tpl_args, args)
 
function h.process_mod_stats(tpl_args, args)
 
local lines = {}
 
local lines = {}
Line 100: Line 412:
 
end
 
end
 
end
 
end
 
--
 
-- Factory
 
--
 
 
h.factory = {}
 
 
function h.factory.cast_text(k, args)
 
args = args or {}
 
return function (tpl_args, frame)
 
tpl_args[args.key_out or k] = m_util.cast.text(tpl_args[k])
 
end
 
end
 
 
function h.factory.damage_html(args)
 
return function(tpl_args, frame)
 
local keys = {
 
min = args.key .. '_damage_min',
 
max = args.key .. '_damage_max',
 
}
 
local value = {}
 
for ktype, key in pairs(keys) do
 
value[ktype] = h.factory.infobox_line{
 
parts = {
 
{
 
key = key,
 
color = false,
 
hide_default = 0,
 
}
 
}
 
}(tpl_args, frame)
 
end
 
if value.min and value.max then
 
local color = args.key 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.key == 'physical' then
 
color = 'mod'
 
end
 
range_fmt = i18n.fmt.variable_damage_range
 
else
 
-- Standard damage range
 
if args.key == 'physical' then
 
color = 'value'
 
end
 
range_fmt = i18n.fmt.standard_damage_range
 
end
 
value = string.format(range_fmt, value.min, value.max)
 
if color then
 
value = m_util.html.poe_color(color, value)
 
end
 
tpl_args[args.key .. '_damage_html'] = value
 
end
 
end
 
end
 
 
-- ----------------------------------------------------------------------------
 
-- Core
 
-- ----------------------------------------------------------------------------
 
 
local core = {}
 
   
 
--
 
--
-- argument mapping
+
-- Argument mapping
 
--
 
--
  +
-- [<tpl_args key>] = {
-- format:
 
  +
-- inherit boolean Whether the item will inherit this key from its base item. Default: true
-- tpl_args key = {
 
  +
-- field string Cargo field name
-- no_copy = true or nil -- When loading an base item, dont copy this key
 
  +
-- type string Cargo field type
-- property = 'prop', -- Property associated with this key
 
-- property_func = function or nil -- Function to unpack the property into a native lua value.
+
-- func function 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, func is used.
 
-- If neither is specified, value is copied as string
 
-- func = function or nil -- Function to unpack the argument into a native lua value and validate it.
 
-- If not specified, value will not be set.
 
-- 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',
Line 186: Line 432:
 
},
 
},
 
html_extra = {
 
html_extra = {
no_copy = true,
+
inherit = false,
 
field = 'html_extra',
 
field = 'html_extra',
 
type = 'Text',
 
type = 'Text',
Line 194: Line 440:
 
field = 'implicit_stat_text',
 
field = 'implicit_stat_text',
 
type = 'Text',
 
type = 'Text',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
tpl_args.implicit_stat_text = h.process_mod_stats(tpl_args, {is_implicit=true})
+
return h.process_mod_stats(tpl_args, {is_implicit=true})
 
end,
 
end,
 
},
 
},
Line 201: Line 447:
 
field = 'explicit_stat_text',
 
field = 'explicit_stat_text',
 
type = 'Text',
 
type = 'Text',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
tpl_args.explicit_stat_text = h.process_mod_stats(tpl_args, {is_implicit=false})
+
local explicit = h.process_mod_stats(tpl_args, {is_implicit=false})
 
 
if tpl_args.is_talisman or tpl_args.is_corrupted then
 
if tpl_args.is_talisman or tpl_args.is_corrupted then
if tpl_args.explicit_stat_text == nil or tpl_args.explicit_stat_text == '' then
+
explicit = explicit or ''
tpl_args.explicit_stat_text = i18n.tooltips.corrupted
+
if explicit ~= '' then
else
+
explicit = explicit .. '<br> '
tpl_args.explicit_stat_text = (tpl_args.explicit_stat_text or '') .. '<br>' .. i18n.tooltips.corrupted
 
 
end
 
end
  +
explicit = explicit .. i18n.tooltips.corrupted
 
end
 
end
  +
return explicit
 
end,
 
end,
 
},
 
},
Line 216: Line 462:
 
field = 'stat_text',
 
field = 'stat_text',
 
type = 'Text',
 
type = 'Text',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
local sep = ''
 
local sep = ''
 
if tpl_args.implicit_stat_text and tpl_args.explicit_stat_text then
 
if tpl_args.implicit_stat_text and tpl_args.explicit_stat_text then
Line 222: Line 468:
 
end
 
end
 
local text = (tpl_args.implicit_stat_text or '') .. sep .. (tpl_args.explicit_stat_text or '')
 
local text = (tpl_args.implicit_stat_text or '') .. sep .. (tpl_args.explicit_stat_text or '')
 
 
if string.len(text) > 0 then
 
if string.len(text) > 0 then
tpl_args.stat_text = text
+
value = text
 
end
 
end
  +
return value
 
end,
 
end,
  +
},
  +
class_id = {
  +
inherit = false,
  +
field = 'class_id',
  +
type = 'String',
  +
func = h.proc.factory.value{
  +
validate = m_util.validate.factory.in_table_keys{
  +
tbl = m_game.constants.item.classes,
  +
errmsg = i18n.errors.invalid_class_id,
  +
},
  +
},
 
},
 
},
 
class = {
 
class = {
no_copy = true,
+
inherit = false,
 
field = 'class',
 
field = 'class',
 
type = 'String',
 
type = 'String',
func = function (tpl_args, frame)
+
func = function (tpl_args, frame, 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,
},
 
-- processed in build_item_classes
 
class_id = {
 
no_copy = true,
 
field = 'class_id',
 
type = 'String',
 
func = function (tpl_args, frame)
 
if m_game.constants.item.classes[tpl_args.class_id] == nil then
 
error(string.format(i18n.errors.invalid_class_id, tostring(tpl_args.class_id)))
 
end
 
end
 
 
},
 
},
 
-- generic
 
-- 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{
  +
validate = m_util.validate.factory.in_table_keys{
if m_game.constants.rarities[tpl_args.rarity_id] == nil then
 
  +
tbl = m_game.constants.rarities,
error(string.format(i18n.errors.invalid_rarity_id, tostring(tpl_args.rarity_id)))
 
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, frame, 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,
 
},
 
},
 
name = {
 
name = {
no_copy = true,
+
inherit = false,
 
field = 'name',
 
field = 'name',
 
type = 'String',
 
type = 'String',
Line 279: Line 527:
 
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 Text',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
tpl_args.drop_rarities_ids = nil
 
tpl_args.drop_rarities_ids = nil
 
if true then return end
 
if true then return end
Line 297: Line 547:
 
return
 
return
 
end
 
end
 
 
if tpl_args.drop_rarities_ids == nil then
 
if tpl_args.drop_rarities_ids == nil then
 
tpl_args.drop_rarities_ids = {}
 
tpl_args.drop_rarities_ids = {}
 
return
 
return
 
end
 
end
 
 
tpl_args.drop_rarities_ids = m_util.string.split(tpl_args.drop_rarities_ids, ',%s*')
 
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
 
for _, rarity_id in ipairs(tpl_args.drop_rarities_ids) do
Line 312: Line 560:
 
},
 
},
 
drop_rarities = {
 
drop_rarities = {
no_copy = true,
+
inherit = false,
 
field = nil,
 
field = nil,
 
type = 'List (,) of Text',
 
type = 'List (,) of Text',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
tpl_args.drop_rarities = nil
 
tpl_args.drop_rarities = nil
 
if true then return end
 
if true then return end
Line 331: Line 579:
 
},
 
},
 
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 = h.proc.boolean,
 
default = true,
 
default = true,
 
},
 
},
 
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 = {
 
drop_leagues = {
no_copy = true,
+
inherit = false,
 
field = 'drop_leagues',
 
field = 'drop_leagues',
 
type = 'List (,) of String',
 
type = 'List (,) of String',
  +
func = h.proc.factory.list{
func = m_util.cast.factory.assoc_table('drop_leagues', {tbl=m_game.constants.leagues, errmsg=i18n.errors.invalid_league}),
 
  +
callback = m_util.validate.factory.in_table_keys{
  +
tbl = m_game.constants.leagues,
  +
errmsg = i18n.errors.invalid_league,
  +
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, frame, value)
if tpl_args.drop_areas ~= nil then
+
value = m_util.cast.table(value)
  +
if value then
tpl_args.drop_areas = m_util.string.split(tpl_args.drop_areas, ',%s*')
 
 
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={'areas._pageName', 'areas.id', 'areas.name', 'areas.main_page'},
 
id_field='areas.id',
 
id_field='areas.id',
id_array=tpl_args.drop_areas,
+
id_array=value,
 
query={limit=5000},
 
query={limit=5000},
 
}
 
}
 
end
 
end
 
 
-- find areas based on item tags for atlas bases
 
-- find areas based on item tags for atlas bases
 
local query_data
 
local query_data
Line 399: Line 653:
 
if query_data ~= nil then
 
if query_data ~= nil then
 
-- in case no manual drop areas have been set
 
-- in case no manual drop areas have been set
if tpl_args.drop_areas == nil then
+
if value == nil then
tpl_args.drop_areas = {}
+
value = {}
 
tpl_args.drop_areas_data = {}
 
tpl_args.drop_areas_data = {}
 
end
 
end
 
local drop_areas_assoc = {}
 
local drop_areas_assoc = {}
for _, id in ipairs(tpl_args.drop_areas) do
+
for _, id in ipairs(value) do
 
drop_areas_assoc[id] = true
 
drop_areas_assoc[id] = true
 
end
 
end
Line 412: Line 666:
 
for _, row in ipairs(query_data) do
 
for _, row in ipairs(query_data) do
 
if drop_areas_assoc[row['areas.id']] == nil then
 
if drop_areas_assoc[row['areas.id']] == nil then
tpl_args.drop_areas[#tpl_args.drop_areas+1] = row['areas.id']
+
value[#value+1] = row['areas.id']
 
tpl_args.drop_areas_data[#tpl_args.drop_areas_data+1] = row
 
tpl_args.drop_areas_data[#tpl_args.drop_areas_data+1] = row
 
else
 
else
Line 425: Line 679:
 
end
 
end
 
end
 
end
  +
return value
 
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,
  +
default = {},
if tpl_args.drop_monsters ~= nil then
 
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 452: Line 705:
 
field = 'required_level',
 
field = 'required_level',
 
type = 'Integer',
 
type = 'Integer',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
tpl_args.required_level_final = tpl_args.required_level
+
value = tpl_args.required_level
  +
if value < cfg.base_item_required_level_threshold then
  +
value = 1
  +
end
  +
return value
 
end,
 
end,
 
default = 1,
 
default = 1,
Line 460: Line 717:
 
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 466: Line 723:
 
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 472: Line 729:
 
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, frame, value)
if tpl_args.class_id == 'DivinationCard' then
+
if not value then
  +
-- Certain types of items have default inventory icons
tpl_args.inventory_icon = tpl_args.inventory_icon or 'Divination card'
 
  +
if i18n.default_inventory_icons[tpl_args.class_id] then
  +
value = i18n.default_inventory_icons[tpl_args.class_id]
  +
elseif tpl_args.base_item_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy' then
  +
value = i18n.default_inventory_icons['Prophecy']
  +
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.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 item 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, frame, 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.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,
 
},
 
},
 
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.text,
 
},
 
},
 
flavour_text_id = {
 
flavour_text_id = {
no_copy = true,
+
inherit = false,
 
field = 'flavour_text_id',
 
field = 'flavour_text_id',
 
type = 'String',
 
type = 'String',
Line 533: Line 790:
 
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)',
 
--type = 'String(unique; size=200)',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
if tpl_args.metadata_id == nil then
+
if value == nil then
return
+
return nil
 
end
 
end
  +
-- Unless we're in testing mode, validate that metadata_id is unique
local results = m_cargo.query(
 
{'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"',
  +
value,
error(string.format(i18n.errors.duplicate_metadata, tpl_args.metadata_id, results[1]['items._pageName']))
 
  +
m_cargo.addslashes(mw.title.getCurrentTitle().fullText)
  +
)
  +
}
  +
)
  +
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,
 
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, frame, 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_relic = {
no_copy = true,
+
inherit = false,
 
field = 'is_relic',
 
field = 'is_relic',
 
type = 'Boolean',
 
type = 'Boolean',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
m_util.cast.factory.boolean('is_relic')(tpl_args, frame)
+
value = m_util.cast.boolean(value)
if tpl_args.is_relic == 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_relic'))
 
error(string.format(i18n.errors.non_unique_flag, 'is_relic'))
 
end
 
end
  +
return value
 
end,
 
end,
 
default = false,
 
default = false,
 
},
 
},
 
is_fated = {
 
is_fated = {
no_copy = true,
+
inherit = false,
 
field = 'is_fated',
 
field = 'is_fated',
 
type = 'Boolean',
 
type = 'Boolean',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
m_util.cast.factory.boolean('is_fated')(tpl_args, frame)
+
value = m_util.cast.boolean(value)
if tpl_args.is_fated == 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_fated'))
 
error(string.format(i18n.errors.non_unique_flag, 'is_fated'))
 
end
 
end
  +
return value
 
end,
 
end,
 
default = false,
 
default = false,
 
},
 
},
 
is_prophecy = {
 
is_prophecy = {
no_copy = true,
+
inherit = false,
 
field = nil,
 
field = nil,
 
type = nil,
 
type = nil,
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
tpl_args._flags.is_prophecy = (tpl_args.metadata_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy' or tpl_args.base_item == 'Prophecy' or tpl_args.base_item_page == 'Prophecy' or tpl_args.base_item_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy')
+
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')
  +
return value
 
end
 
end
 
},
 
},
 
is_blight_item = {
 
is_blight_item = {
no_copy = true,
+
inherit = false,
 
field = nil,
 
field = nil,
 
type = nil,
 
type = nil,
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
tpl_args._flags.is_blight_item = (tpl_args.blight_item_tier ~= nil)
 
tpl_args._flags.is_blight_item = (tpl_args.blight_item_tier ~= nil)
  +
return value
 
end
 
end
 
},
 
},
 
is_drop_restricted = {
 
is_drop_restricted = {
no_copy = true,
+
inherit = false,
 
field = 'is_drop_restricted',
 
field = 'is_drop_restricted',
 
type = 'Boolean',
 
type = 'Boolean',
func = m_util.cast.factory.boolean('is_drop_restricted'),
+
func = h.proc.boolean,
default = function(tpl_args, frame)
+
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
 
-- 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
+
for _, key in ipairs({'is_talisman', 'is_essence', 'is_fated', 'is_replica', 'is_relic', 'drop_monsters'}) do
if tpl_args[var] then
+
-- key 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
 
return true
 
end
 
end
Line 669: Line 948:
 
},
 
},
 
purchase_costs = {
 
purchase_costs = {
func = function(tpl_args, frame)
+
field = nil,
  +
type = nil,
  +
func = function (tpl_args, frame, 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 696: Line 977:
 
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, frame)
 
if tpl_args.rarity_id ~= 'unique' then
 
if tpl_args.rarity_id ~= 'unique' then
 
return
 
return
Line 733: Line 1,012:
 
end
 
end
 
end,
 
end,
  +
},
  +
is_sellable = {
  +
inherit = false,
  +
field = nil,
  +
type = nil,
  +
func = h.proc.boolean,
  +
default = true,
 
},
 
},
 
sell_prices_override = {
 
sell_prices_override = {
no_copy = true,
+
inherit = false,
func = function(tpl_args, frame)
+
field = nil,
  +
type = nil,
  +
func = function (tpl_args, frame, 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 760: Line 1,049:
 
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 766: Line 1,054:
 
break
 
break
 
end
 
end
  +
return value
 
end,
 
end,
 
},
 
},
Line 771: Line 1,060:
 
-- 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
 
-- Can be set manually, but default to Q20 for unique weapons/body armours
 
-- Also 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, frame)
+
func = function (tpl_args, frame, value)
local quality = tonumber(tpl_args.quality)
+
local quality = tonumber(value)
--
 
 
if quality == nil then
 
if quality == nil then
 
if tpl_args.rarity_id ~= 'unique' then
 
if tpl_args.rarity_id ~= 'unique' then
Line 791: Line 1,079:
 
end
 
end
 
end
 
end
 
tpl_args.quality = quality
 
 
 
local stat = {
 
local stat = {
 
min = quality,
 
min = quality,
Line 799: Line 1,084:
 
avg = quality,
 
avg = quality,
 
}
 
}
  +
core.stats_update(tpl_args, 'quality', stat, nil, '_stats')
 
h.stats_update(tpl_args, 'quality', stat, nil, '_stats')
 
 
 
if tpl_args.class_id == 'UtilityFlask' or tpl_args.class_id == 'UtilityFlaskCritical' then
 
if tpl_args.class_id == 'UtilityFlask' or tpl_args.class_id == 'UtilityFlaskCritical' then
h.stats_update(tpl_args, 'quality_flask_duration', stat, nil, '_stats')
+
core.stats_update(tpl_args, 'quality_flask_duration', stat, nil, '_stats')
 
-- quality is added to quantity for maps
 
-- quality is added to quantity for maps
 
elseif tpl_args.class_id == 'Map' then
 
elseif tpl_args.class_id == 'Map' then
h.stats_update(tpl_args, 'map_item_drop_quantity_+%', stat, nil, '_stats')
+
core.stats_update(tpl_args, 'map_item_drop_quantity_+%', stat, nil, '_stats')
 
end
 
end
  +
return quality
 
end,
 
end,
 
},
 
},
Line 814: Line 1,098:
 
field = 'is_talisman',
 
field = 'is_talisman',
 
type = 'Boolean',
 
type = 'Boolean',
func = m_util.cast.factory.boolean('is_talisman'),
+
func = h.proc.boolean,
 
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 858: Line 1,140:
 
field = 'buff_values',
 
field = 'buff_values',
 
type = 'List (,) of Integer',
 
type = 'List (,) of Integer',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
local values = {}
 
local values = {}
 
local i = 0
 
local i = 0
Line 870: Line 1,152:
 
-- 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, frame, 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 886: Line 1,169:
 
field = 'icon',
 
field = 'icon',
 
type = 'String',
 
type = 'String',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
tpl_args.buff_icon = string.format(i18n.status_icon, tpl_args.name)
+
return string.format(i18n.files.status_icon, tpl_args.name)
 
end,
 
end,
 
},
 
},
Line 895: Line 1,178:
 
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 = 'Integer',
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,
 
},
 
},
 
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,
 
},
 
},
 
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 926: Line 1,209:
 
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 932: Line 1,215:
 
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 938: Line 1,221:
 
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 944: Line 1,227:
 
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 950: Line 1,233:
 
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 956: Line 1,239:
 
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 962: Line 1,245:
 
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,
 
},
 
},
Line 969: Line 1,252:
 
field = 'armour',
 
field = 'armour',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('armour'),
+
func = h.proc.number,
 
default = 0,
 
default = 0,
 
},
 
},
Line 975: Line 1,258:
 
field = 'energy_shield',
 
field = 'energy_shield',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('energy_shield'),
+
func = h.proc.number,
 
default = 0,
 
default = 0,
 
},
 
},
Line 981: Line 1,264:
 
field = 'evasion',
 
field = 'evasion',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('evasion'),
+
func = h.proc.number,
  +
default = 0,
  +
},
  +
ward = {
  +
field = 'ward',
  +
type = 'Integer',
  +
func = h.proc.number,
 
default = 0,
 
default = 0,
 
},
 
},
Line 988: Line 1,277:
 
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 995: Line 1,284:
 
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,001: Line 1,290:
 
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, frame, 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,035: Line 1,323:
 
field = 'gem_tags',
 
field = 'gem_tags',
 
type = 'List (,) of String',
 
type = 'List (,) of String',
-- TODO: default rework
+
func = h.proc.factory.list{
  +
callback = m_util.validate.factory.in_table_keys{
func = function(tpl_args, frame)
 
  +
tbl = m_game.constants.item.gem_tags_lookup,
if tpl_args.gem_tags then
 
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 = {},
 
},
 
},
 
-- Support gems only
 
-- Support gems only
Line 1,052: Line 1,341:
 
field = 'support_gem_letter_html',
 
field = 'support_gem_letter_html',
 
type = 'Text',
 
type = 'Text',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, 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', k)
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,
 
},
 
},
Line 1,088: Line 1,365:
 
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 = {
Line 1,103: Line 1,380:
 
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(size=1)',
func_copy = function(tpl_args, frame)
 
tpl_args.map_guild_character = tpl_args.unique_map_guild_character
 
end,
 
 
func = nil,
 
func = nil,
  +
func_copy = function (tpl_args, frame, value)
  +
tpl_args.map_guild_character = value
  +
end,
 
},
 
},
 
unique_map_area_id = {
 
unique_map_area_id = {
Line 1,117: Line 1,394:
 
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, frame, value)
tpl_args.map_area_id = tpl_args.unique_map_area_id
+
tpl_args.map_area_id = value
 
end,
 
end,
 
},
 
},
Line 1,124: Line 1,401:
 
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, frame, value)
tpl_args.map_area_level = tpl_args.unique_map_area_level
+
tpl_args.map_area_level = value
 
end,
 
end,
 
},
 
},
Line 1,132: Line 1,409:
 
field = 'series',
 
field = 'series',
 
type = 'String',
 
type = 'String',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, 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,
 
},
 
},
Line 1,142: Line 1,420:
 
field = 'x',
 
field = 'x',
 
type = 'Float',
 
type = 'Float',
func = m_util.cast.factory.number('atlas_x'),
+
func = h.proc.number,
 
},
 
},
 
atlas_y = {
 
atlas_y = {
 
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 = {
Line 1,157: Line 1,435:
 
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 = {
 
field = 'x0',
 
field = 'x0',
 
type = 'Float',
 
type = 'Float',
func = m_util.cast.factory.number('atlas_x0'),
+
func = h.proc.number,
 
},
 
},
 
atlas_x1 = {
 
atlas_x1 = {
 
field = 'x1',
 
field = 'x1',
 
type = 'Float',
 
type = 'Float',
func = m_util.cast.factory.number('atlas_x1'),
+
func = h.proc.number,
 
},
 
},
 
atlas_x2 = {
 
atlas_x2 = {
 
field = 'x2',
 
field = 'x2',
 
type = 'Float',
 
type = 'Float',
func = m_util.cast.factory.number('atlas_x2'),
+
func = h.proc.number,
 
},
 
},
 
atlas_x3 = {
 
atlas_x3 = {
 
field = 'x3',
 
field = 'x3',
 
type = 'Float',
 
type = 'Float',
func = m_util.cast.factory.number('atlas_x3'),
+
func = h.proc.number,
 
},
 
},
 
atlas_x4 = {
 
atlas_x4 = {
 
field = 'x4',
 
field = 'x4',
 
type = 'Float',
 
type = 'Float',
func = m_util.cast.factory.number('atlas_x4'),
+
func = h.proc.number,
 
},
 
},
 
atlas_y0 = {
 
atlas_y0 = {
 
field = 'y0',
 
field = 'y0',
 
type = 'Float',
 
type = 'Float',
func = m_util.cast.factory.number('atlas_0'),
+
func = h.proc.number,
 
},
 
},
 
atlas_y1 = {
 
atlas_y1 = {
 
field = 'y1',
 
field = 'y1',
 
type = 'Float',
 
type = 'Float',
func = m_util.cast.factory.number('atlas_y1'),
+
func = h.proc.number,
 
},
 
},
 
atlas_y2 = {
 
atlas_y2 = {
 
field = 'y2',
 
field = 'y2',
 
type = 'Float',
 
type = 'Float',
func = m_util.cast.factory.number('atlas_y2'),
+
func = h.proc.number,
 
},
 
},
 
atlas_y3 = {
 
atlas_y3 = {
 
field = 'y3',
 
field = 'y3',
 
type = 'Float',
 
type = 'Float',
func = m_util.cast.factory.number('atlas_y3'),
+
func = h.proc.number,
 
},
 
},
 
atlas_y4 = {
 
atlas_y4 = {
 
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 = {
 
field = nil,
 
field = nil,
 
type = nil,
 
type = nil,
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
tpl_args.atlas_connections = {}
+
value = {}
 
 
local cont = true
 
local cont = true
 
local i = 1
 
local i = 1
Line 1,260: Line 1,537:
 
end
 
end
 
 
tpl_args.atlas_connections[data.map2] = data
+
value[data.map2] = data
 
table.insert(tpl_args._subobjects, data)
 
table.insert(tpl_args._subobjects, 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,
 
 
},
 
},
 
--
 
--
Line 1,280: Line 1,556:
 
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 = {
 
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 = {
 
field = 'description',
 
field = 'description',
 
type = 'Text',
 
type = 'Text',
func = h.factory.cast_text('description'),
+
func = h.proc.text,
 
},
 
},
 
cosmetic_type = {
 
cosmetic_type = {
 
field = 'cosmetic_type',
 
field = 'cosmetic_type',
 
type = 'String',
 
type = 'String',
func = h.factory.cast_text('cosmetic_type'),
+
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,
  +
},
  +
},
 
},
 
},
 
-- for essences
 
-- for essences
 
is_essence = {
 
is_essence = {
 
field = nil,
 
field = nil,
func = m_util.cast.factory.boolean('is_essence'),
+
type = nil,
  +
func = h.proc.boolean,
 
default = false,
 
default = false,
 
},
 
},
Line 1,306: Line 1,588:
 
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 = {
 
field = 'level',
 
field = 'level',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('essence_level'),
+
func = h.proc.number,
 
},
 
},
 
essence_type = {
 
essence_type = {
 
field = 'type',
 
field = 'type',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('essence_type'),
+
func = h.proc.number,
 
},
 
},
 
essence_category = {
 
essence_category = {
Line 1,327: Line 1,609:
 
field = 'tier',
 
field = 'tier',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('blight_item_tier'),
+
func = h.proc.number,
 
},
 
},
 
-- harvest seeds
 
-- harvest seeds
Line 1,333: Line 1,615:
 
field = 'type_id',
 
field = 'type_id',
 
type = 'String',
 
type = 'String',
  +
func = nil,
 
},
 
},
 
seed_type = {
 
seed_type = {
 
field = 'type',
 
field = 'type',
 
type = 'String',
 
type = 'String',
func = function (tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
if tpl_args.seed_type_id ~= 'none' or tpl_args.seed_type_id ~= nil then
 
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]
+
value = m_game.seed_types[tpl_args.seed_type_id]
 
end
 
end
  +
return value
 
end
 
end
 
},
 
},
Line 1,346: Line 1,630:
 
field = nil,
 
field = nil,
 
type = nil,
 
type = nil,
func = function (tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
if tpl_args.seed_type ~= nil then
 
if tpl_args.seed_type ~= nil then
tpl_args.seed_type_html = m_util.html.poe_color(tpl_args.seed_type_id, tpl_args.seed_type)
+
value = m_util.html.poe_color(tpl_args.seed_type_id, tpl_args.seed_type)
 
end
 
end
  +
return value
 
end
 
end
 
},
 
},
Line 1,355: Line 1,640:
 
field = 'effect',
 
field = 'effect',
 
type = 'Text',
 
type = 'Text',
  +
func = nil,
 
},
 
},
 
seed_tier = {
 
seed_tier = {
 
field = 'tier',
 
field = 'tier',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('seed_tier'),
+
func = h.proc.number,
 
},
 
},
 
seed_growth_cycles = {
 
seed_growth_cycles = {
 
field = 'growth_cycles',
 
field = 'growth_cycles',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('seed_growth_cycles'),
+
func = h.proc.number,
 
},
 
},
 
seed_required_nearby_seed_tier = {
 
seed_required_nearby_seed_tier = {
 
field = 'required_nearby_seed_tier',
 
field = 'required_nearby_seed_tier',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('seed_required_nearby_seed_tier'),
+
func = h.proc.number,
 
},
 
},
 
seed_required_nearby_seed_amount = {
 
seed_required_nearby_seed_amount = {
 
field = 'required_nearby_seed_amount',
 
field = 'required_nearby_seed_amount',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('seed_required_nearby_seed_amount'),
+
func = h.proc.number,
 
},
 
},
 
seed_consumed_wild_lifeforce_percentage = {
 
seed_consumed_wild_lifeforce_percentage = {
 
field = 'consumed_wild_lifeforce_percentage',
 
field = 'consumed_wild_lifeforce_percentage',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('seed_consumed_wild_lifeforce_percentage'),
+
func = h.proc.number,
 
default = 0,
 
default = 0,
 
},
 
},
Line 1,385: Line 1,671:
 
field = 'consumed_vivid_lifeforce_percentage',
 
field = 'consumed_vivid_lifeforce_percentage',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('seed_consumed_vivid_lifeforce_percentage'),
+
func = h.proc.number,
 
default = 0,
 
default = 0,
 
},
 
},
Line 1,391: Line 1,677:
 
field = 'consumed_primal_lifeforce_percentage',
 
field = 'consumed_primal_lifeforce_percentage',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('seed_consumed_primal_lifeforce_percentage'),
+
func = h.proc.number,
 
default = 0,
 
default = 0,
 
},
 
},
Line 1,397: Line 1,683:
 
field = 'granted_craft_option_ids',
 
field = 'granted_craft_option_ids',
 
type = 'List (,) of String',
 
type = 'List (,) of String',
func = m_util.cast.factory.number('seed_grandted_craft_option_ids'),
+
func = h.proc.list,
default = 0,
+
default = {},
 
},
 
},
 
--
 
--
Line 1,406: Line 1,692:
 
field = 'radius',
 
field = 'radius',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('plant_booster_radius'),
+
func = h.proc.number,
 
},
 
},
 
plant_booster_lifeforce = {
 
plant_booster_lifeforce = {
 
field = 'lifeforce',
 
field = 'lifeforce',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('plant_booster_lifeforce'),
+
func = h.proc.number,
 
},
 
},
 
plant_booster_additional_crafting_options = {
 
plant_booster_additional_crafting_options = {
 
field = 'additional_crafting_options',
 
field = 'additional_crafting_options',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('plant_booster_additional_crafting_options'),
+
func = h.proc.number,
 
},
 
},
 
plant_booster_extra_chances = {
 
plant_booster_extra_chances = {
 
field = 'extra_chances',
 
field = 'extra_chances',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('plant_booster_extra_chances'),
+
func = h.proc.number,
 
},
 
},
 
--
 
--
Line 1,429: Line 1,715:
 
field = 'required_job_id',
 
field = 'required_job_id',
 
type = 'String',
 
type = 'String',
func = h.factory.cast_text('heist_required_job_id'),
+
func = h.proc.text,
 
},
 
},
 
heist_required_job_level = {
 
heist_required_job_level = {
 
field = 'required_job_level',
 
field = 'required_job_level',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('heist_required_job_level'),
+
func = h.proc.number,
 
},
 
},
 
heist_data = {
 
heist_data = {
func = function (tpl_args, frame)
+
field = nil,
  +
type = nil,
  +
func = function (tpl_args, frame, value)
 
if tpl_args.heist_required_job_level then
 
if tpl_args.heist_required_job_level then
 
if tpl_args.heist_required_job_id then
 
if tpl_args.heist_required_job_id then
Line 1,448: Line 1,736:
 
}
 
}
 
)
 
)
 
 
local npcs = {}
 
local npcs = {}
 
 
for _, row in ipairs(results) do
 
for _, row in ipairs(results) do
 
npcs[#npcs+1] = row['heist_npcs.name']
 
npcs[#npcs+1] = row['heist_npcs.name']
 
end
 
end
 
 
tpl_args.heist_required_npcs = table.concat(npcs, ', ')
 
tpl_args.heist_required_npcs = table.concat(npcs, ', ')
 
tpl_args.heist_required_job = results[1]['heist_jobs.name']
 
tpl_args.heist_required_job = results[1]['heist_jobs.name']
Line 1,461: Line 1,746:
 
end
 
end
 
end
 
end
  +
return value
 
end,
 
end,
 
},
 
},
Line 1,469: Line 1,755:
 
field = 'is_master_doodad',
 
field = 'is_master_doodad',
 
type = 'Boolean',
 
type = 'Boolean',
func = m_util.cast.factory.boolean('is_master_doodad'),
+
func = h.proc.boolean,
 
},
 
},
 
master = {
 
master = {
 
field = 'master',
 
field = 'master',
 
type = 'String',
 
type = 'String',
  +
func = h.proc.factory.value{
-- todo validate against list of master names
 
func = m_util.cast.factory.table('master', {key='full', tbl=m_game.constants.masters}),
+
validate = m_util.validate.factory.in_table_keys{
  +
tbl = m_util.table.column(m_game.constants.masters, 'long_upper', 'full'),
  +
errmsg = i18n.errors.invalid_master,
  +
},
  +
},
 
},
 
},
 
master_level_requirement = {
 
master_level_requirement = {
 
field = 'level_requirement',
 
field = 'level_requirement',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('master_level_requirement'),
+
func = h.proc.number,
 
},
 
},
 
master_favour_cost = {
 
master_favour_cost = {
 
field = 'favour_cost',
 
field = 'favour_cost',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('master_favour_cost'),
+
func = h.proc.number,
 
},
 
},
 
variation_count = {
 
variation_count = {
 
field = 'variation_count',
 
field = 'variation_count',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('variation_count'),
+
func = h.proc.number,
 
},
 
},
 
-- Propehcy
 
-- Propehcy
Line 1,501: Line 1,791:
 
field = 'prediction_text',
 
field = 'prediction_text',
 
type = 'Text',
 
type = 'Text',
func = h.factory.cast_text('prediction_text'),
+
func = h.proc.text,
 
},
 
},
 
seal_cost = {
 
seal_cost = {
 
field = 'seal_cost',
 
field = 'seal_cost',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('seal_cost'),
+
func = h.proc.number,
 
},
 
},
 
prophecy_reward = {
 
prophecy_reward = {
 
field = 'reward',
 
field = 'reward',
 
type = 'Text',
 
type = 'Text',
func = h.factory.cast_text('prophecy_reward'),
+
func = h.proc.text,
 
},
 
},
 
prophecy_objective = {
 
prophecy_objective = {
 
field = 'objective',
 
field = 'objective',
 
type = 'Text',
 
type = 'Text',
func = h.factory.cast_text('prophecy_objective'),
+
func = h.proc.text,
 
},
 
},
 
-- Divination cards
 
-- Divination cards
Line 1,522: Line 1,812:
 
field = 'card_art',
 
field = 'card_art',
 
type = 'Page',
 
type = 'Page',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
tpl_args.card_art = string.format(i18n.divination_card_art, tpl_args.card_art or tpl_args.name)
+
return string.format(i18n.files.divination_card_art, value or tpl_args.name)
 
end,
 
end,
 
},
 
},
Line 1,532: Line 1,822:
 
-- For rarity != normal, rarity already verified
 
-- For rarity != normal, rarity already verified
 
base_item = {
 
base_item = {
no_copy = true,
+
inherit = false,
 
field = 'base_item',
 
field = 'base_item',
 
type = 'String',
 
type = 'String',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
tpl_args.base_item = tpl_args.base_item_data['items.name']
+
return tpl_args.base_item_data['items.name']
 
end,
 
end,
 
},
 
},
 
base_item_id = {
 
base_item_id = {
no_copy = true,
+
inherit = false,
 
field = 'base_item_id',
 
field = 'base_item_id',
 
type = 'String',
 
type = 'String',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
tpl_args.base_item_id = tpl_args.base_item_data['items.metadata_id']
+
return tpl_args.base_item_data['items.metadata_id']
 
end,
 
end,
 
},
 
},
 
base_item_page = {
 
base_item_page = {
no_copy = true,
+
inherit = false,
 
field = 'base_item_page',
 
field = 'base_item_page',
 
type = 'Page',
 
type = 'Page',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
tpl_args.base_item_page = tpl_args.base_item_data['items._pageName']
+
return tpl_args.base_item_data['items._pageName']
 
end,
 
end,
 
},
 
},
 
name_list = {
 
name_list = {
no_copy = true,
+
inherit = false,
 
field = 'name_list',
 
field = 'name_list',
 
type = 'List (�) of String',
 
type = 'List (�) of String',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
if tpl_args.name_list ~= nil then
+
value = m_util.cast.table(value)
tpl_args.name_list = m_util.string.split(tpl_args.name_list, ',%s*')
+
value[#value+1] = tpl_args.name
  +
return value
tpl_args.name_list[#tpl_args.name_list+1] = tpl_args.name
 
else
 
tpl_args.name_list = {tpl_args.name}
 
end
 
 
end,
 
end,
  +
default = {},
 
},
 
},
 
frame_type = {
 
frame_type = {
no_copy = true,
+
inherit = false,
 
field = 'frame_type',
 
field = 'frame_type',
 
type = 'String',
 
type = 'String',
property = nil,
+
func = function (tpl_args, frame, value)
func = function(tpl_args, frame)
+
if value then
  +
return value
  +
end
 
if tpl_args._flags.is_prophecy then
 
if tpl_args._flags.is_prophecy then
tpl_args.frame_type = 'prophecy'
+
return 'prophecy'
return
 
 
end
 
end
 
 
local var = cfg.class_specifics[tpl_args.class_id]
 
local var = cfg.class_specifics[tpl_args.class_id]
 
if var ~= nil and var.frame_type ~= nil then
 
if var ~= nil and var.frame_type ~= nil then
tpl_args.frame_type = var.frame_type
+
return var.frame_type
return
 
 
end
 
end
 
 
if tpl_args.is_relic then
 
if tpl_args.is_relic then
tpl_args.frame_type = 'relic'
+
return 'relic'
return
 
 
end
 
end
+
return tpl_args.rarity_id
tpl_args.frame_type = tpl_args.rarity_id
 
 
end,
 
end,
 
},
 
},
Line 1,597: Line 1,881:
 
--
 
--
 
mods = {
 
mods = {
default = function (tpl_args, frame) return {} end,
+
field = nil,
  +
type = nil,
  +
func = nil,
  +
default = {},
 
func_fetch = function (tpl_args, frame)
 
func_fetch = function (tpl_args, frame)
  +
-- Fetch implicit mods from base item
 
local results = m_cargo.query(
 
local results = m_cargo.query(
 
{'items' ,'item_mods'},
 
{'items' ,'item_mods'},
Line 1,613: Line 1,901:
 
result = row['item_mods.text']
 
result = row['item_mods.text']
 
end
 
end
tpl_args._mods[#tpl_args._mods+1] = {
+
tpl_args._base_implicit_mods[#tpl_args._base_implicit_mods+1] = {
 
result=result,
 
result=result,
 
id=row['item_mods.id'],
 
id=row['item_mods.id'],
Line 1,624: Line 1,912:
 
},
 
},
 
physical_damage_html = {
 
physical_damage_html = {
no_copy = true,
+
inherit = false,
 
field = 'physical_damage_html',
 
field = 'physical_damage_html',
 
type = 'Text',
 
type = 'Text',
func = h.factory.damage_html{key='physical'},
+
func = h.proc.factory.damage_html{type = 'physical'},
 
},
 
},
 
fire_damage_html = {
 
fire_damage_html = {
no_copy = true,
+
inherit = false,
 
field = 'fire_damage_html',
 
field = 'fire_damage_html',
 
type = 'Text',
 
type = 'Text',
func = h.factory.damage_html{key='fire'},
+
func = h.proc.factory.damage_html{type = 'fire'},
 
},
 
},
 
cold_damage_html = {
 
cold_damage_html = {
no_copy = true,
+
inherit = false,
 
field = 'cold_damage_html',
 
field = 'cold_damage_html',
 
type = 'Text',
 
type = 'Text',
func = h.factory.damage_html{key='cold'},
+
func = h.proc.factory.damage_html{type = 'cold'},
 
},
 
},
 
lightning_damage_html = {
 
lightning_damage_html = {
no_copy = true,
+
inherit = false,
 
field = 'lightning_damage_html',
 
field = 'lightning_damage_html',
 
type = 'Text',
 
type = 'Text',
func = h.factory.damage_html{key='lightning'},
+
func = h.proc.factory.damage_html{type = 'lightning'},
 
},
 
},
 
chaos_damage_html = {
 
chaos_damage_html = {
no_copy = true,
+
inherit = false,
 
field = 'chaos_damage_html',
 
field = 'chaos_damage_html',
 
type = 'Text',
 
type = 'Text',
func = h.factory.damage_html{key='chaos'},
+
func = h.proc.factory.damage_html{type = 'chaos'},
 
},
 
},
 
damage_avg = {
 
damage_avg = {
no_copy = true,
+
inherit = false,
 
field = 'damage_avg',
 
field = 'damage_avg',
 
type = 'Text',
 
type = 'Text',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
local dmg = {min=0, max=0}
 
local dmg = {min=0, max=0}
 
for key, _ in pairs(dmg) do
 
for key, _ in pairs(dmg) do
Line 1,664: Line 1,952:
 
end
 
end
 
end
 
end
 
 
dmg = (dmg.min + dmg.max) / 2
 
dmg = (dmg.min + dmg.max) / 2
+
return dmg
tpl_args.damage_avg = dmg
 
 
end,
 
end,
 
},
 
},
 
damage_html = {
 
damage_html = {
no_copy = true,
+
inherit = false,
 
field = 'damage_html',
 
field = 'damage_html',
 
type = 'Text',
 
type = 'Text',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
local text = {}
 
local text = {}
 
for _, dkey in ipairs(m_game.constants.damage_type_order) do
 
for _, dkey in ipairs(m_game.constants.damage_type_order) do
local value = tpl_args[dkey .. '_damage_html']
+
local range = tpl_args[dkey .. '_damage_html']
if value ~= nil then
+
if range ~= nil then
text[#text+1] = value
+
text[#text+1] = range
 
end
 
end
 
end
 
end
 
if #text > 0 then
 
if #text > 0 then
tpl_args.damage_html = table.concat(text, '<br>')
+
value = table.concat(text, '<br>')
 
end
 
end
  +
return value
 
end,
 
end,
 
},
 
},
 
item_limit = {
 
item_limit = {
no_copy = true,
+
inherit = false,
 
field = 'item_limit',
 
field = 'item_limit',
 
type = 'Integer',
 
type = 'Integer',
func = m_util.cast.factory.number('item_limit'),
+
func = h.proc.number,
 
},
 
},
 
jewel_radius_html = {
 
jewel_radius_html = {
no_copy = true,
+
inherit = false,
 
field = 'radius_html',
 
field = 'radius_html',
 
type = 'Text',
 
type = 'Text',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
-- Get radius from stats
 
-- Get radius from stats
 
local radius = tpl_args._stats.local_jewel_effect_base_radius
 
local radius = tpl_args._stats.local_jewel_effect_base_radius
Line 1,704: Line 1,991:
 
local size = m_game.constants.item.jewel_radius_to_size[radius] or radius
 
local size = m_game.constants.item.jewel_radius_to_size[radius] or radius
 
local color = radius == 0 and 'mod' or 'value'
 
local color = radius == 0 and 'mod' or 'value'
tpl_args.jewel_radius_html = m_util.html.poe_color(color, size)
+
value = m_util.html.poe_color(color, size)
 
end
 
end
  +
return value
 
end,
 
end,
 
},
 
},
 
incubator_effect = {
 
incubator_effect = {
no_copy = true,
+
inherit = false,
 
field = 'effect',
 
field = 'effect',
 
type = 'Text',
 
type = 'Text',
Line 1,715: Line 2,003:
 
},
 
},
 
drop_areas_html = {
 
drop_areas_html = {
no_copy = true,
+
inherit = false,
 
field = 'drop_areas_html',
 
field = 'drop_areas_html',
 
type = 'Text',
 
type = 'Text',
func = function(tpl_args, frame)
+
func = function (tpl_args, frame, value)
 
if tpl_args.drop_areas_data == nil then
 
if tpl_args.drop_areas_data == nil then
return
+
return value
 
end
 
end
+
if value ~= nil then
if tpl_args.drop_areas_html ~= nil then
+
return value
return
 
 
end
 
end
 
 
local areas = {}
 
local areas = {}
 
for _, data in pairs(tpl_args.drop_areas_data) do
 
for _, data in pairs(tpl_args.drop_areas_data) do
Line 1,734: Line 2,020:
 
end
 
end
 
end
 
end
+
return table.concat(areas, ' • ')
tpl_args.drop_areas_html = table.concat(areas, ' • ')
 
 
end,
 
end,
 
},
 
},
 
release_version = {
 
release_version = {
no_copy = true,
+
inherit = false,
 
field = 'release_version',
 
field = 'release_version',
type = 'String'
+
type = 'String',
  +
func = nil,
 
},
 
},
 
removal_version = {
 
removal_version = {
no_copy = true,
+
inherit = false,
 
field = 'removal_version',
 
field = 'removal_version',
 
type = 'String',
 
type = 'String',
  +
func = nil,
 
},
 
},
 
--
 
--
Line 1,752: Line 2,039:
 
--
 
--
 
suppress_improper_modifiers_category = {
 
suppress_improper_modifiers_category = {
no_copy = true,
+
inherit = false,
 
field = nil,
 
field = nil,
func = m_util.cast.factory.boolean('suppress_improper_modifiers_category'),
+
func = h.proc.boolean,
 
default = false,
 
default = false,
 
},
 
},
 
upgraded_from_disabled = {
 
upgraded_from_disabled = {
no_copy = true,
+
inherit = false,
 
field = nil,
 
field = nil,
func = m_util.cast.factory.boolean('upgraded_from_disabled'),
+
func = h.proc.boolean,
 
default = false,
 
default = false,
 
},
 
},
Line 2,060: Line 2,347:
 
stats_override = {
 
stats_override = {
 
['local_unique_tabula_rasa_no_requirement_or_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 = {
  +
field = 'ward',
  +
stats_add = {
  +
'local_ward',
  +
},
  +
stats_increased = {
  +
'local_ward_+%',
  +
'quality',
 
},
 
},
 
minimum = 0,
 
minimum = 0,
Line 2,129: Line 2,430:
   
 
core.dps_map = {
 
core.dps_map = {
{
+
physical_dps = {
name = 'physical_dps',
 
 
field = 'physical_dps',
 
field = 'physical_dps',
damage_args = {'physical_damage', },
+
damage_args = {'physical_damage'},
 
label_infobox = i18n.tooltips.physical_dps,
 
label_infobox = i18n.tooltips.physical_dps,
 
html_fmt_options = {
 
html_fmt_options = {
Line 2,139: Line 2,439:
 
},
 
},
 
},
 
},
{
+
fire_dps = {
name = 'fire_dps',
 
 
field = 'fire_dps',
 
field = 'fire_dps',
 
damage_args = {'fire_damage'},
 
damage_args = {'fire_damage'},
Line 2,149: Line 2,448:
 
},
 
},
 
},
 
},
{
+
cold_dps = {
name = 'cold_dps',
 
 
field = 'cold_dps',
 
field = 'cold_dps',
 
damage_args = {'cold_damage'},
 
damage_args = {'cold_damage'},
Line 2,159: Line 2,457:
 
},
 
},
 
},
 
},
{
+
lightning_dps = {
name = 'lightning_dps',
 
 
field = 'lightning_dps',
 
field = 'lightning_dps',
 
damage_args = {'lightning_damage'},
 
damage_args = {'lightning_damage'},
Line 2,169: Line 2,466:
 
},
 
},
 
},
 
},
{
+
chaos_dps = {
name = 'chaos_dps',
 
 
field = 'chaos_dps',
 
field = 'chaos_dps',
 
damage_args = {'chaos_damage'},
 
damage_args = {'chaos_damage'},
Line 2,179: Line 2,475:
 
},
 
},
 
},
 
},
{
+
elemental_dps = {
name = 'elemental_dps',
 
 
field = 'elemental_dps',
 
field = 'elemental_dps',
 
damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'},
 
damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'},
Line 2,189: Line 2,484:
 
},
 
},
 
},
 
},
{
+
poison_dps = {
name = 'poison_dps',
 
 
field = 'poison_dps',
 
field = 'poison_dps',
 
damage_args = {'physical_damage', 'chaos_damage'},
 
damage_args = {'physical_damage', 'chaos_damage'},
Line 2,199: Line 2,493:
 
},
 
},
 
},
 
},
{
+
dps = {
name = 'dps',
 
 
field = 'dps',
 
field = 'dps',
 
damage_args = {'physical_damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'},
 
damage_args = {'physical_damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'},

Revision as of 01:07, 16 July 2021

Template info icon Module documentation[view] [edit] [history] [purge]

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

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

-- 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
     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. 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
       -- 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
                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 value = tpl_args._stats[data.key]
                if value ~= nil then
                    base_values[i] = value.min
                    temp_values[#temp_values+1] = {value=value, index=i}
                end
            end
        else
            -- Regular. Look for key exactly as written in tpl_args
            for i, data in ipairs(args.parts) do
                base_values[i] = tpl_args[data.key]
                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
                    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, frame, 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 or opt.hide_default == v.max then
                        hide = true
                    end
                else
                    local v = data.value
                    if opt.hide_default == v.min or 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, frame, 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.stats_update(tpl_args, id, value, modid, key)
    if tpl_args[key][id] == nil then
        tpl_args[key][id] = {
            references = {modid},
            min = value.min,
            max = value.max,
            avg = value.avg,
        }
    else
        if modid ~= nil then
            table.insert(tpl_args[key][id].references, modid)
        end
        tpl_args[key][id].min = tpl_args[key][id].min + value.min
        tpl_args[key][id].max = tpl_args[key][id].max + value.max
        tpl_args[key][id].avg = tpl_args[key][id].avg + value.avg
    end
end

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

function h.proc.factory.value(args)
    args = args or {}
    return function (tpl_args, frame, 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, frame, 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, frame, 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, frame)
        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

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.list = h.proc.factory.list()

-- Process mod stats
function h.process_mod_stats(tpl_args, args)
    local lines = {}
    
    local skip = cfg.class_specifics[tpl_args.class_id]
    if skip then
        skip = skip.skip_stat_lines
    end 
    
    local random_mods = {}
    
    for _, modinfo in ipairs(tpl_args._mods) do
        if modinfo.is_implicit == args.is_implicit then
            if modinfo.is_random == true then
                if random_mods[modinfo.stat_text] then
                    table.insert(random_mods[modinfo.stat_text], modinfo)
                else
                    random_mods[modinfo.stat_text] = {modinfo}
                end
            else
                if modinfo.id == nil then
                    table.insert(lines, modinfo.result)
                -- Allows the override of the SMW fetched mod texts for this modifier via <modtype><id>_text parameter
                elseif modinfo.text ~= nil then
                     table.insert(lines, modinfo.text)
                else
                    for _, line in ipairs(m_util.string.split(modinfo.result['mods.stat_text'] or '', '<br>')) do
                        if line ~= '' then
                            if skip == nil then
                                table.insert(lines, line)
                            else
                                local skipped = false
                                for _, pattern in ipairs(skip) do
                                    if string.match(line, pattern) then
                                        skipped = true
                                        break
                                    end
                                end
                                if not skipped then
                                    table.insert(lines, line)
                                end
                            end
                        end
                    end
                end
            end
        end
    end
    
    for stat_text, modinfo_list in pairs(random_mods) do
        local text = {}
        for _, modinfo in ipairs(modinfo_list) do
            table.insert(text, modinfo.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
    
    if #lines == 0 then
        return
    else
        return table.concat(lines, '<br>')
    end
end

--
-- 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
-- }
core.map = {
    -- special params
    html = {
        inherit = false,
        field = 'html',
        type = 'Text',
        func = nil,
    },
    html_extra = {
        inherit = false,
        field = 'html_extra',
        type = 'Text',
        func = nil,
    },
    implicit_stat_text = {
        field = 'implicit_stat_text',
        type = 'Text',
        func = function (tpl_args, frame, value)
            return h.process_mod_stats(tpl_args, {is_implicit=true})
        end,
    },
    explicit_stat_text = {
        field = 'explicit_stat_text',
        type = 'Text',
        func = function (tpl_args, frame, value)
            local explicit = h.process_mod_stats(tpl_args, {is_implicit=false})
            if tpl_args.is_talisman or tpl_args.is_corrupted then
                explicit = explicit or '' 
                if explicit ~= '' then
                    explicit = explicit .. '<br> '
                end
                explicit = explicit .. i18n.tooltips.corrupted
            end
            return explicit
        end,
    },
    stat_text = {
        field = 'stat_text',
        type = 'Text',
        func = function (tpl_args, frame, value)
            local sep = ''
            if tpl_args.implicit_stat_text and tpl_args.explicit_stat_text then
                sep = string.format('<span class="item-stat-separator -%s"></span>', tpl_args.frame_type)
            end
            local text = (tpl_args.implicit_stat_text or '') .. sep .. (tpl_args.explicit_stat_text or '')
            if string.len(text) > 0 then
                value = text
            end
            return value
        end,
    },
    class_id = {
        inherit = false,
        field = 'class_id',
        type = 'String',
        func = h.proc.factory.value{
            validate = m_util.validate.factory.in_table_keys{
                tbl = m_game.constants.item.classes,
                errmsg = i18n.errors.invalid_class_id,
            },
        },
    },
    class = {
        inherit = false,
        field = 'class',
        type = 'String',
        func = function (tpl_args, frame, 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,
    },
    -- generic
    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, frame, value)
            return m_game.constants.rarities[tpl_args.rarity_id]['long_upper']
        end,
    },
    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 Text',
        func = function (tpl_args, frame, value)
            tpl_args.drop_rarities_ids = nil
            if true then return end
            -- Drop rarities only matter for base items.
            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,
    },
    drop_rarities = {
        inherit = false,
        field = nil,
        type = 'List (,) of Text',
        func = function (tpl_args, frame, value)
            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 = {
        inherit = false,
        field = 'drop_enabled',
        type = 'Boolean',
        func = h.proc.boolean,
        default = true,
    },
    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,
    },
    drop_leagues = {
        inherit = false,
        field = 'drop_leagues',
        type = 'List (,) of String',
        func = h.proc.factory.list{
            callback = m_util.validate.factory.in_table_keys{
                tbl = m_game.constants.leagues,
                errmsg = i18n.errors.invalid_league,
                errlvl = 4,
            },
        },
        default = {},
    },
    drop_areas = {
        inherit = false,
        field = 'drop_areas',
        type = 'List (,) of String',
        func = function (tpl_args, frame, value)
            value = m_util.cast.table(value)
            if value then
                tpl_args.drop_areas_data = m_cargo.array_query{
                    tables={'areas'},
                    fields={'areas._pageName', 'areas.id', 'areas.name', 'areas.main_page'},
                    id_field='areas.id',
                    id_array=value,
                    query={limit=5000},
                }
            end
            -- 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 value == nil then
                        value = {}
                        tpl_args.drop_areas_data = {}
                    end
                    local drop_areas_assoc = {}
                    for _, id in ipairs(value) 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
                            value[#value+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
            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, frame, value)
            value = tpl_args.required_level
            if value < cfg.base_item_required_level_threshold 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, frame, 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]
                elseif tpl_args.base_item_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy' then
                    value = i18n.default_inventory_icons['Prophecy']
                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, frame, 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,
    },
    flavour_text = {
        inherit = false,
        field = 'flavour_text',
        type = 'Text',
        func = h.proc.text,
    },
    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',
        --type = 'String(unique; size=200)',
        func = function (tpl_args, frame, 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().fullText)
                        )
                    }
                )
                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_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, frame, 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_relic = {
        inherit = false,
        field = 'is_relic',
        type = 'Boolean',
        func = function (tpl_args, frame, 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_relic'))
            end
            return value
        end,
        default = false,
    },
    is_fated = {
        inherit = false,
        field = 'is_fated',
        type = 'Boolean',
        func = function (tpl_args, frame, 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_fated'))
            end
            return value
        end,
        default = false,
    },
    is_prophecy = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, frame, value)
            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')
            return value
        end
    },
    is_blight_item = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, frame, value)
            tpl_args._flags.is_blight_item = (tpl_args.blight_item_tier ~= nil)
            return value
        end
    },
    is_drop_restricted = {
        inherit = false,
        field = 'is_drop_restricted',
        type = 'Boolean',
        func = h.proc.boolean,
        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 _, key in ipairs({'is_talisman', 'is_essence', 'is_fated', 'is_replica', 'is_relic', 'drop_monsters'}) do
                -- key 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
            for _, flag in ipairs({'is_prophecy', 'is_blight_item'}) do
                if tpl_args._flags[flag] then
                    return true
                end
            end
            return false
        end,
    },
    purchase_costs = {
        field = nil,
        type = nil,
        func = function (tpl_args, frame, 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
                        
                        tpl_args._subobjects[#tpl_args._subobjects+1] = {
                            _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, frame)
            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
                
                tpl_args._subobjects[#tpl_args._subobjects+1] = {
                    _table = 'item_purchase_costs',
                    amount = values.amount,
                    name = values.name,
                    rarity = values.rarity,
                }
            end
        end,
    },
    is_sellable = {
        inherit = false,
        field = nil,
        type = nil,
        func = h.proc.boolean,
        default = true,
    },
    sell_prices_override = {
        inherit = false,
        field = nil,
        type = nil,
        func = function (tpl_args, frame, 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
                    tpl_args._subobjects[#tpl_args._subobjects+1] = {
                        _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',
        -- Can be set manually, but default to Q20 for unique weapons/body armours
        -- Also must copy to stat for the stat adjustments to work properly
        func = function (tpl_args, frame, value)
            local quality = tonumber(value)
            if quality == nil then
                if tpl_args.rarity_id ~= 'unique' then
                    quality = 0
                elseif cfg.class_groups.weapons.keys[tpl_args.class_id] or cfg.class_groups.armor.keys[tpl_args.class_id] then
                    quality = 20
                else
                    quality = 0
                end
            end
            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
            return quality
        end,
    },
    -- amulets
    is_talisman = {
        field = 'is_talisman',
        type = 'Boolean',
        func = h.proc.boolean,
        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, frame, 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, frame, 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, frame, 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 = 'Integer',
        func = h.proc.number,
    },
    physical_damage_min = {
        field = 'physical_damage_min',
        type = 'Integer',
        func = h.proc.number,
    },
    physical_damage_max = {
        field = 'physical_damage_max',
        type = 'Integer',
        func = h.proc.number,
    },
    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 = {
        field = 'armour',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    energy_shield = {
        field = 'energy_shield',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    evasion = {
        field = 'evasion',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    ward = {
        field = 'ward',
        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, frame, 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 = {},
    },
    -- Support gems only
    support_gem_letter = {
        field = 'support_gem_letter',
        type = 'String(size=1)',
        func = nil,
    },
    support_gem_letter_html = {
        field = 'support_gem_letter_html',
        type = 'Text',
        func = function (tpl_args, frame, 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', k)
                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,
    },
    --
    -- Maps
    --
    map_tier = {
        field = 'tier',
        type = 'Integer',
        func = h.proc.number,
    },
    map_guild_character = {
        field = 'guild_character',
        type = 'String(size=1)',
        func = nil,
    },
    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(size=1)',
        func = nil,
        func_copy = function (tpl_args, frame, 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, frame, 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, frame, value)
            tpl_args.map_area_level = value
        end,
    },
    map_series = {
        field = 'series',
        type = 'String',
        func = function (tpl_args, frame, 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 = {
        field = 'x',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y = {
        field = 'y',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_region_id = {
        field = 'region_id',
        type = 'String',
        func = nil,
    },
    atlas_region_minimum = {
        field = 'region_minimum',
        type = 'Integer',
        func = h.proc.number,
    },
    atlas_x0 = {
        field = 'x0',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_x1 = {
        field = 'x1',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_x2 = {
        field = 'x2',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_x3 = {
        field = 'x3',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_x4 = {
        field = 'x4',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y0 = {
        field = 'y0',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y1 = {
        field = 'y1',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y2 = {
        field = 'y2',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y3 = {
        field = 'y3',
        type = 'Float',
        func = h.proc.number,
    },
    atlas_y4 = {
        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 = {
        field = nil,
        type = nil,
        func = function (tpl_args, frame, 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._subobjects, data)
                else
                    cont = false
                    if i == 1 then
                        value = nil
                    end
                end
                i = i + 1
            end
            return value
        end,
    },
    --
    -- Currency-like items
    --
    stack_size = {
        field = 'stack_size',
        type = 'Integer',
        func = h.proc.number,
    },
    stack_size_currency_tab = {
        field = 'stack_size_currency_tab',
        type = 'Integer',
        func = h.proc.number,
    },
    description = {
        field = 'description',
        type = 'Text',
        func = h.proc.text,
    },
    cosmetic_type = {
        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,
            },
        },
    },
    -- for essences
    is_essence = {
        field = nil,
        type = nil,
        func = h.proc.boolean,
        default = false,
    },
    essence_level_restriction = {
        field = 'level_restriction',
        type = 'Integer',
        func = h.proc.number,
    },
    essence_level = {
        field = 'level',
        type = 'Integer',
        func = h.proc.number,
    },
    essence_type = {
        field = 'type',
        type = 'Integer',
        func = h.proc.number,
    },
    essence_category = {
        field = 'category',
        type = 'String',
        func = nil,
    },
    -- blight crafting items (i.e. oils)
    blight_item_tier = {
        field = 'tier',
        type = 'Integer',
        func = h.proc.number,
    },
    -- harvest seeds
    seed_type_id = {
        field = 'type_id',
        type = 'String',
        func = nil,
    },
    seed_type = {
        field = 'type',
        type = 'String',
        func = function (tpl_args, frame, 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 = {
        field = nil,
        type = nil,
        func = function (tpl_args, frame, 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 = {
        field = 'effect',
        type = 'Text',
        func = nil,
    },
    seed_tier = {
        field = 'tier',
        type = 'Integer',
        func = h.proc.number,
    },
    seed_growth_cycles = {
        field = 'growth_cycles',
        type = 'Integer',
        func = h.proc.number,
    },
    seed_required_nearby_seed_tier = {
        field = 'required_nearby_seed_tier',
        type = 'Integer',
        func = h.proc.number,
    },
    seed_required_nearby_seed_amount = {
        field = 'required_nearby_seed_amount',
        type = 'Integer',
        func = h.proc.number,
    },
    seed_consumed_wild_lifeforce_percentage = {
        field = 'consumed_wild_lifeforce_percentage',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    seed_consumed_vivid_lifeforce_percentage = {
        field = 'consumed_vivid_lifeforce_percentage',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    seed_consumed_primal_lifeforce_percentage = {
        field = 'consumed_primal_lifeforce_percentage',
        type = 'Integer',
        func = h.proc.number,
        default = 0,
    },
    seed_granted_craft_option_ids = {
        field = 'granted_craft_option_ids',
        type = 'List (,) of String',
        func = h.proc.list,
        default = {},
    },
    --
    -- harvest planet boosters
    --
    plant_booster_radius = {
        field = 'radius',
        type = 'Integer',
        func = h.proc.number,
    },
    plant_booster_lifeforce = {
        field = 'lifeforce',
        type = 'Integer',
        func = h.proc.number,
    },
    plant_booster_additional_crafting_options = {
        field = 'additional_crafting_options',
        type = 'Integer',
        func = h.proc.number,
    },
    plant_booster_extra_chances = {
        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 = {
        field = nil,
        type = nil,
        func = function (tpl_args, frame, value)
            if tpl_args.heist_required_job_level then
                if tpl_args.heist_required_job_id then
                    local results = m_cargo.query(
                        {'heist_jobs', 'heist_npcs', 'heist_npc_skills'},
                        {'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 doodads (HideoutDoodads.dat)
    --
    is_master_doodad = {
        field = 'is_master_doodad',
        type = 'Boolean',
        func = h.proc.boolean,
    },
    master = {
        field = 'master',
        type = 'String',
        func = h.proc.factory.value{
            validate = m_util.validate.factory.in_table_keys{
                tbl = m_util.table.column(m_game.constants.masters, 'long_upper', 'full'),
                errmsg = i18n.errors.invalid_master,
            },
        },
    },
    master_level_requirement = {
        field = 'level_requirement',
        type = 'Integer',
        func = h.proc.number,
    },
    master_favour_cost = {
        field = 'favour_cost',
        type = 'Integer',
        func = h.proc.number,
    },
    variation_count = {
        field = 'variation_count',
        type = 'Integer',
        func = h.proc.number,
    },
    -- Propehcy
    prophecy_id = {
        field = 'prophecy_id',
        type = 'String',
        func = nil,
    },
    prediction_text = {
        field = 'prediction_text',
        type = 'Text',
        func = h.proc.text,
    },
    seal_cost = {
        field = 'seal_cost',
        type = 'Integer',
        func = h.proc.number,
    },
    prophecy_reward = {
        field = 'reward',
        type = 'Text',
        func = h.proc.text,
    },
    prophecy_objective = {
        field = 'objective',
        type = 'Text',
        func = h.proc.text,
    },
    -- Divination cards
    card_art = {
        field = 'card_art',
        type = 'Page',
        func = function (tpl_args, frame, value)
            return string.format(i18n.files.divination_card_art, value or tpl_args.name)
        end,
    },
    -- ------------------------------------------------------------------------
    -- derived stats
    -- ------------------------------------------------------------------------
    
    -- For rarity != normal, rarity already verified
    base_item = {
        inherit = false,
        field = 'base_item',
        type = 'String',
        func = function (tpl_args, frame, value)
            return tpl_args.base_item_data['items.name']
        end,
    },
    base_item_id = {
        inherit = false,
        field = 'base_item_id',
        type = 'String',
        func = function (tpl_args, frame, value)
            return tpl_args.base_item_data['items.metadata_id']
        end,
    },
    base_item_page = {
        inherit = false,
        field = 'base_item_page',
        type = 'Page',
        func = function (tpl_args, frame, value)
            return tpl_args.base_item_data['items._pageName']
        end,
    },
    name_list = {
        inherit = false,
        field = 'name_list',
        type = 'List (�) of String',
        func = function (tpl_args, frame, 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, frame, 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
            if tpl_args.is_relic then
                return 'relic'
            end
            return tpl_args.rarity_id
        end,
    },
    --
    -- args populated by mod validation
    -- 
    mods = {
        field = nil,
        type = nil,
        func = nil,
        default = {},
        func_fetch = function (tpl_args, frame)
            -- Fetch implicit mods from base item
            local results = m_cargo.query(
                {'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._base_implicit_mods[#tpl_args._base_implicit_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 = {
        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, frame, value)
            local dmg = {min=0, max=0}
            for key, _ in pairs(dmg) do
                for _, dkey in ipairs(m_game.constants.damage_type_order) do
                    dmg[key] = dmg[key] + tpl_args[string.format('%s_damage_%s_range_average', dkey, key)]
                end
            end
            dmg = (dmg.min + dmg.max) / 2
            return dmg
        end,
    },
    damage_html = {
        inherit = false,
        field = 'damage_html',
        type = 'Text',
        func = function (tpl_args, frame, 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,
    },
    item_limit = {
        inherit = false,
        field = 'item_limit',
        type = 'Integer',
        func = h.proc.number,
    },
    jewel_radius_html = {
        inherit = false,
        field = 'radius_html',
        type = 'Text',
        func = function (tpl_args, frame, 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,
    },
    incubator_effect = {
        inherit = false,
        field = 'effect',
        type = 'Text',
        func = nil,
    },
    drop_areas_html = {
        inherit = false,
        field = 'drop_areas_html',
        type = 'Text',
        func = function (tpl_args, frame, 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['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
            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,
    },
    upgraded_from_disabled = {
        inherit = false,
        field = nil,
        func = h.proc.boolean,
        default = false,
    },
}

core.stat_map = {
    required_level_final = {
        field = 'required_level',
        stats_add = {
            'local_level_requirement_+',
        },
        stats_override = {
            ['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=1, max=1},
        },
        minimum = 1,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    weapon_range = {
        field = 'weapon_range',
        stats_add = {
            'local_weapon_range_+',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    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 = {
            color = 'fire',
            fmt = '%i',
        },
    },
    fire_damage_max = {
        field = 'fire_damage_max',
        stats_add = {
            'local_maximum_added_fire_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'fire',
            fmt = '%i',
        },
    },
    cold_damage_min = {
        field = 'cold_damage_min',
        stats_add = {
            'local_minimum_added_cold_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'cold',
            fmt = '%i',
        },
    },
    cold_damage_max = {
        field = 'cold_damage_max',
        stats_add = {
            'local_maximum_added_cold_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'cold',
            fmt = '%i',
        },
    },
    lightning_damage_min = {
        field = 'lightning_damage_min',
        stats_add = {
            'local_minimum_added_lightning_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'lightning',
            fmt = '%i',
        },
    },
    lightning_damage_max = {
        field = 'lightning_damage_max',
        stats_add = {
            'local_maximum_added_lightning_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'lightning',
            fmt = '%i',
        },
    },
    chaos_damage_min = {
        field = 'chaos_damage_min',
        stats_add = {
            'local_minimum_added_chaos_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'chaos',
            fmt = '%i',
        },
    },
    chaos_damage_max = {
        field = 'chaos_damage_max',
        stats_add = {
            'local_maximum_added_chaos_damage',
        },
        minimum = 0,
        html_fmt_options = {
            color = 'chaos',
            fmt = '%i',
        },
    },
    critical_strike_chance = {
        field = 'critical_strike_chance',
        stats_add = {
            'local_critical_strike_chance',
        },
        stats_increased = {
            'local_critical_strike_chance_+%',
        },
        stats_override = {
            ['local_weapon_always_crit'] = {min=100, max=100},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%.2f%%',
        },
    },
    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',
        },
    },
    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',
        },
    },
    block = {
        field = 'block',
        stats_add = {
            'local_additional_block_chance_%',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i%%',
        },
    },
    armour = {
        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 = {
        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 = {
        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_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    ward = {
        field = 'ward',
        stats_add = {
            'local_ward',
        },
        stats_increased = {
            'local_ward_+%',
            'quality',
        },
        minimum = 0,
        html_fmt_options = {
            fmt = '%i',
        },
    },
    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,
        },
    },
}

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

return core