Module:Item2/core

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

-- 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 = {}

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 _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 , ' ')) 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, '')) :done :done table.insert(lines, tostring(tbl)) end if #lines == 0 then return else return table.concat(lines, ' ') 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

-- -- Core --

local core = {}

core.factory = {}

function core.factory.infobox_line(args) --   args:     type: How to read data from tpl_args using the given keys. nil = Regular, gem = Skill progression, stat = Stats     parts:      [n]:       key: key to use       allow_zero: allow zero values       hide_default: hide the value if this is set       hide_default_key: key to use if it isn't equal to the key parameter       truncate: set to true to truncate the line       -- from m_util.html.format_value --       func: Function to transform the value retrieved from the database       fmt: Format string (or function that returns format string) to use for the value. Default: '%s'       fmt_range: Format string to use for the value range. Default: '(%s-%s)'       color: poe_color code to use for the value range. False for no color. Default: 'mod'       class: Additional css class added to color tag       inline: Format string to use for the output       inline_color: poe_color code to use for the output. False for no color. Default: 'default'       inline_class: Additional css class added to inline color tag     sep: If specified, parts are joined with this separator before being formatted for output     fmt: Format string to use for output. If not specified, parts are simply concatenated     color: poe_color code to use for output. Default: no color     class: Additional css class added to output    --

args.parts = args.parts or {} return function (tpl_args, frame) local base_values = {} local temp_values = {} if args.type == 'gem' then -- Skill progression. Look for keys in tpl_args.skill_levels if not cfg.class_groups.gems.keys[tpl_args.class_id] then -- Skip if this item is not actually a gem return end for i, data in ipairs(args.parts) do               local value = tpl_args.skill_levels[0][data.key] if value ~= nil then base_values[i] = value temp_values[#temp_values+1] = {value={min=value,max=value}, index=i} else value = { min=tpl_args.skill_levels[1][data.key], max=tpl_args.skill_levels[tpl_args.max_level][data.key], }                   if value.min == nil or value.max == nil then else base_values[i] = value.min temp_values[#temp_values+1] = {value=value, index=i} 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 insert = false if opt.hide_default == nil then insert = true elseif opt.hide_default_key == nil then local v = data.value if opt.hide_default ~= v.min and opt.hide_default ~= v.max then insert = true end else local v = { min = tpl_args[opt.hide_default_key .. '_range_minimum'], max = tpl_args[opt.hide_default_key .. '_range_maximum'], }               if v.min == nil or v.max == nil then if opt.hide_default ~= tpl_args[opt.hide_default_key] then insert = true end elseif opt.hide_default ~= v.min and opt.hide_default ~= v.max then insert = true end end if insert == true then table.insert(final_values, data) end end -- all zeros = dont display and return early if #final_values == 0 then return nil end 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 if options.truncate then options.inline_class = (options.inline_class or '') .. ' u-truncate-line' end parts[#parts+1] = m_util.html.format_value(tpl_args, frame, value, options) end 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.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] = core.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

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

-- -- argument mapping -- -- format: -- tpl_args key = { --  no_copy = true or nil           -- When loading an base item, dont copy this key --  property = 'prop',              -- Property associated with this key --  property_func = function or nil -- Function to unpack the property into a native lua value. --                                     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 = { -- special params html = { no_copy = true, field = 'html', type = 'Text', func = nil, },   html_extra = { no_copy = true, field = 'html_extra', type = 'Text', func = nil, },   implicit_stat_text = { field = 'implicit_stat_text', type = 'Text', func = function(tpl_args, frame) tpl_args.implicit_stat_text = h.process_mod_stats(tpl_args, {is_implicit=true}) end, },   explicit_stat_text = { field = 'explicit_stat_text', type = 'Text', func = function(tpl_args, frame) tpl_args.explicit_stat_text = h.process_mod_stats(tpl_args, {is_implicit=false}) 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 tpl_args.explicit_stat_text = i18n.tooltips.corrupted else tpl_args.explicit_stat_text = (tpl_args.explicit_stat_text or '') .. ' ' .. i18n.tooltips.corrupted end end end, },   stat_text = { field = 'stat_text', type = 'Text', func = function(tpl_args, frame) local sep = '' if tpl_args.implicit_stat_text and tpl_args.explicit_stat_text then sep = string.format(' ', 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 tpl_args.stat_text = text end end, },   class = { no_copy = true, field = 'class', type = 'String', func = function (tpl_args, frame) tpl_args.class = m_game.constants.item.classes[tpl_args.class_id]['long_upper'] -- Avoids errors with empty item class names later on           if tpl_args.class == '' then tpl_args.class = nil 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 rarity_id = { no_copy = true, field = 'rarity_id', type = 'String', func = function (tpl_args, frame) if m_game.constants.rarities[tpl_args.rarity_id] == nil then error(string.format(i18n.errors.invalid_rarity_id, tostring(tpl_args.rarity_id))) end end },   rarity = { no_copy = true, field = 'rarity', type = 'String', func = function(tpl_args, frame) tpl_args.rarity = m_game.constants.rarities[tpl_args.rarity_id]['long_upper'] end },   name = { no_copy = true, field = 'name', type = 'String', func = nil, },   size_x = { field = 'size_x', type = 'Integer', func = m_util.cast.factory.number('size_x'), },   size_y = { field = 'size_y', type = 'Integer', func = m_util.cast.factory.number('size_y'), },   drop_rarities_ids = { no_copy = true, field = 'drop_rarity_ids', type = 'List of Text', func = function(tpl_args, frame) 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 = { no_copy = true, field = nil, type = 'List of Text', func = function(tpl_args, frame) tpl_args.drop_rarities = nil if true then return end -- Drop rarities only matter for base items. if tpl_args.rarity_id ~= 'normal' then return end local rarities = {} for _, rarity_id in ipairs(tpl_args.drop_rarities_ids) do               rarities[#rarities+1] = m_game.constants.rarities[rarity_id].full end tpl_args.drop_rarities = rarities end, },   drop_enabled = { no_copy = true, field = 'drop_enabled', type = 'Boolean', func = m_util.cast.factory.boolean('drop_enabled'), default = true, },   drop_level = { no_copy = true, field = 'drop_level', type = 'Integer', func = m_util.cast.factory.number('drop_level'), },   drop_level_maximum = { no_copy = true, field = 'drop_level_maximum', type = 'Integer', func = m_util.cast.factory.number('drop_level_maximum'), },   drop_leagues = { no_copy = true, field = 'drop_leagues', type = 'List of String', func = m_util.cast.factory.assoc_table('drop_leagues', {tbl=m_game.constants.leagues, errmsg=i18n.errors.invalid_league}), },   drop_areas = { no_copy = true, field = 'drop_areas', type = 'List of String', func = function(tpl_args, frame) if tpl_args.drop_areas ~= nil then tpl_args.drop_areas = m_util.string.split(tpl_args.drop_areas, ',%s*') 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=tpl_args.drop_areas, 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 tpl_args.drop_areas == nil then tpl_args.drop_areas = {} tpl_args.drop_areas_data = {} end local drop_areas_assoc = {} for _, id in ipairs(tpl_args.drop_areas) do                       drop_areas_assoc[id] = true end local duplicates = {} for _, row in ipairs(query_data) do                       if drop_areas_assoc[row['areas.id']] == nil then tpl_args.drop_areas[#tpl_args.drop_areas+1] = row['areas.id'] tpl_args.drop_areas_data[#tpl_args.drop_areas_data+1] = row else duplicates[#duplicates+1] = row['areas.id'] end end if #duplicates > 0 then tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.duplicate_area_id_from_query, table.concat(duplicates, ', ')) tpl_args._flags.duplicate_query_area_ids = true end end end end, },   drop_monsters = { no_copy = true, field = 'drop_monsters', type = 'List of Text', func = function (tpl_args, frame) if tpl_args.drop_monsters ~= nil then tpl_args.drop_monsters = m_util.string.split(tpl_args.drop_monsters, ',%s*') end end, },   drop_text = { no_copy = true, field = 'drop_text', type = 'Text', func = h.factory.cast_text('drop_text'), },   required_level = { field = 'required_level_base', type = 'Integer', func = m_util.cast.factory.number('required_level'), default = 1, },   required_level_final = { field = 'required_level', type = 'Integer', func = function(tpl_args, frame) tpl_args.required_level_final = tpl_args.required_level end, default = 1, },   required_dexterity = { field = 'required_dexterity', type = 'Integer', func = m_util.cast.factory.number('required_dexterity'), default = 0, },   required_strength = { field = 'required_strength', type = 'Integer', func = m_util.cast.factory.number('required_strength'), default = 0, },   required_intelligence = { field = 'required_intelligence', type = 'Integer', func = m_util.cast.factory.number('required_intelligence'), default = 0, },   inventory_icon = { no_copy = true, field = 'inventory_icon', type = 'String', func = function(tpl_args, frame) if not tpl_args.inventory_icon then -- Certain types of items have default inventory icons if i18n.default_inventory_icons[tpl_args.class_id] then tpl_args.inventory_icon = i18n.default_inventory_icons[tpl_args.class_id] elseif tpl_args.base_item_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy' then tpl_args.inventory_icon = i18n.default_inventory_icons['Prophecy'] end end tpl_args.inventory_icon_id = tpl_args.inventory_icon or tpl_args.name tpl_args.inventory_icon = 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 = { no_copy = true, field = 'alternate_art_inventory_icons', type = 'List of String', func = function(tpl_args, frame) local icons = {} if tpl_args.alternate_art_inventory_icons ~= nil then local names = m_util.string.split(tpl_args.alternate_art_inventory_icons, ',%s*') for _, name in ipairs(names) do                   icons[#icons+1] = string.format(i18n.files.inventory_icon, string.format('%s %s', tpl_args.inventory_icon_id, name)) end end tpl_args.alternate_art_inventory_icons = icons end, default = function (tpl_args, frame) return {} end, },   cannot_be_traded_or_modified = { no_copy = true, field = 'cannot_be_traded_or_modified', type = 'Boolean', func = m_util.cast.factory.boolean('cannot_be_traded_or_modified'), default = false, },   help_text = { debug_ignore_nil = true, field = 'help_text', type = 'Text', func = h.factory.cast_text('help_text'), },   flavour_text = { no_copy = true, field = 'flavour_text', type = 'Text', func = h.factory.cast_text('flavour_text'), },   flavour_text_id = { no_copy = true, field = 'flavour_text_id', type = 'String', func = nil, },   tags = { field = 'tags', type = 'List of String', func = m_util.cast.factory.assoc_table('tags', {           tbl = m_game.constants.tags,            errmsg = i18n.errors.invalid_tag,        }), },   metadata_id = { no_copy = true, field = 'metadata_id', type = 'String', --type = 'String(unique; size=200)', func = function(tpl_args, frame) if tpl_args.metadata_id == nil then return end local results = m_cargo.query(               {'items'},                {'items._pageName'},                {                    where=string.format('items.metadata_id="%s" AND items._pageName != "%s"', tpl_args.metadata_id, mw.title.getCurrentTitle.fullText)                }            ) if #results > 0 and tpl_args.debug_id == nil and not tpl_args.debug then error(string.format(i18n.errors.duplicate_metadata, tpl_args.metadata_id, results[1]['items._pageName'])) end end, },   influences = { no_copy = true, field = 'influences', type = 'List of String', func = m_util.cast.factory.assoc_table('influences', {           tbl = m_game.constants.influences,            errmsg = i18n.errors.invalid_influence,        }), },   is_fractured = { no_copy = true, field = 'is_fractured', type = 'Boolean', func = m_util.cast.factory.boolean('is_fractured'), default = false, },   is_synthesised = { no_copy = true, field = 'is_synthesised', type = 'Boolean', func = m_util.cast.factory.boolean('is_synthesised'), default = false, },   is_veiled = { no_copy = true, field = 'is_veiled', type = 'Boolean', func = m_util.cast.factory.boolean('is_veiled'), default = false, },   is_replica = { no_copy = true, field = 'is_replica', type = 'Boolean', func = function(tpl_args, frame) m_util.cast.factory.boolean('is_replica')(tpl_args, frame) if tpl_args.is_replica == true and tpl_args.rarity_id ~= 'unique' then error(string.format(i18n.errors.non_unique_flag, 'is_replica')) end end, default = false, },   is_corrupted = { no_copy = true, field = 'is_corrupted', type = 'Boolean', func = m_util.cast.factory.boolean('is_corrupted'), default = false, },   is_relic = { no_copy = true, field = 'is_relic', type = 'Boolean', func = function(tpl_args, frame) m_util.cast.factory.boolean('is_relic')(tpl_args, frame) if tpl_args.is_relic == true and tpl_args.rarity_id ~= 'unique' then error(string.format(i18n.errors.non_unique_flag, 'is_relic')) end end, default = false, },   is_fated = { no_copy = true, field = 'is_fated', type = 'Boolean', func = function(tpl_args, frame) m_util.cast.factory.boolean('is_fated')(tpl_args, frame) if tpl_args.is_fated == true and tpl_args.rarity_id ~= 'unique' then error(string.format(i18n.errors.non_unique_flag, 'is_fated')) end end, default = false, },   is_prophecy = { no_copy = true, field = nil, type = nil, func = function(tpl_args, frame) tpl_args._flags.is_prophecy = (tpl_args.metadata_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy' or tpl_args.base_item == cfg.prophecy_base_item or tpl_args.base_item_page == cfg.prophecy_base_item_page or tpl_args.base_item_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy') end },   is_blight_item = { no_copy = true, field = nil, type = nil, func = function(tpl_args, frame) tpl_args._flags.is_blight_item = (tpl_args.blight_item_tier ~= nil) end },   is_drop_restricted = { no_copy = true, field = 'is_drop_restricted', type = 'Boolean', func = m_util.cast.factory.boolean('is_drop_restricted'), default = function(tpl_args, frame) -- Generally items that are obtained only from specific monsters can't be obtained via cards or other means unless specifically specified for _, var in ipairs({'is_talisman', 'is_fated', 'is_relic', 'drop_monsters'}) do               if tpl_args[var] then return true end end 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 = { func = function(tpl_args, frame) 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 tpl_args.purchase_costs = 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, },   sell_prices_override = { no_copy = true, func = function(tpl_args, frame) -- these variables are also used by mods when setting automatic sell prices tpl_args.sell_prices = {} tpl_args.sell_price_order = {} 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 end, },   --    -- specific section --   -- Most item classes quality = { no_copy = true, 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) local quality = tonumber(tpl_args.quality) --            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 tpl_args.quality = quality local stat = { min = quality, max = quality, avg = quality, }           core.stats_update(tpl_args, 'quality', stat, nil, '_stats') if tpl_args.class_id == 'UtilityFlask' or tpl_args.class_id == 'UtilityFlaskCritical' then core.stats_update(tpl_args, 'quality_flask_duration', stat, nil, '_stats') -- quality is added to quantity for maps elseif tpl_args.class_id == 'Map' then core.stats_update(tpl_args, 'map_item_drop_quantity_+%', stat, nil, '_stats') end end, },   -- amulets is_talisman = { field = 'is_talisman', type = 'Boolean', func = m_util.cast.factory.boolean('is_talisman'), default = false, },   talisman_tier = { field = 'talisman_tier', type = 'Integer', func = m_util.cast.factory.number('talisman_tier'), },   -- flasks charges_max = { field = 'charges_max', type = 'Integer', func = m_util.cast.factory.number('charges_max'), },   charges_per_use = { field = 'charges_per_use', type = 'Integer', func = m_util.cast.factory.number('charges_per_use'), },   flask_mana = { field = 'mana', type = 'Integer', func = m_util.cast.factory.number('flask_mana'), },   flask_life = { field = 'life', type = 'Integer', func = m_util.cast.factory.number('flask_life'), },   flask_duration = { field = 'duration', type = 'Float', func = m_util.cast.factory.number('flask_duration'), },   buff_id = { field = 'id', type = 'String', func = nil, },   buff_values = { field = 'buff_values', type = 'List of Integer', func = function(tpl_args, frame) 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 tpl_args.buff_values = values end end, func_copy = function(tpl_args, frame) tpl_args.buff_values = m_util.string.split(tpl_args.buff_values, ',%s*') end, default = function (tpl_args, frame) return {} end, },   buff_stat_text = { field = 'stat_text', type = 'String', func = nil, },   buff_icon = { field = 'icon', type = 'String', func = function(tpl_args, frame) tpl_args.buff_icon = string.format(i18n.files.status_icon, tpl_args.name) end, },   -- weapons critical_strike_chance = { field = 'critical_strike_chance', type = 'Float', func = m_util.cast.factory.number('critical_strike_chance'), },   attack_speed = { field = 'attack_speed', type = 'Float', func = m_util.cast.factory.number('attack_speed'), },   weapon_range = { field = 'weapon_range', type = 'Integer', func = m_util.cast.factory.number('weapon_range'), },   physical_damage_min = { field = 'physical_damage_min', type = 'Integer', func = m_util.cast.factory.number('physical_damage_min'), },   physical_damage_max = { field = 'physical_damage_max', type = 'Integer', func = m_util.cast.factory.number('physical_damage_max'), },   fire_damage_min = { field = 'fire_damage_min', type = 'Integer', func = m_util.cast.factory.number('fire_damage_min'), default = 0, },   fire_damage_max = { field = 'fire_damage_max', type = 'Integer', func = m_util.cast.factory.number('fire_damage_max'), default = 0, },   cold_damage_min = { field = 'cold_damage_min', type = 'Integer', func = m_util.cast.factory.number('cold_damage_min'), default = 0, },   cold_damage_max = { field = 'cold_damage_max', type = 'Integer', func = m_util.cast.factory.number('cold_damage_max'), default = 0, },   lightning_damage_min = { field = 'lightning_damage_min', type = 'Integer', func = m_util.cast.factory.number('lightning_damage_min'), default = 0, },   lightning_damage_max = { field = 'lightning_damage_max', type = 'Integer', func = m_util.cast.factory.number('lightning_damage_max'), default = 0, },   chaos_damage_min = { field = 'chaos_damage_min', type = 'Integer', func = m_util.cast.factory.number('chaos_damage_min'), default = 0, },   chaos_damage_max = { field = 'chaos_damage_max', type = 'Integer', func = m_util.cast.factory.number('chaos_damage_max'), default = 0, },   -- armor-type stuff armour = { field = 'armour', type = 'Integer', func = m_util.cast.factory.number('armour'), default = 0, },   energy_shield = { field = 'energy_shield', type = 'Integer', func = m_util.cast.factory.number('energy_shield'), default = 0, },   evasion = { field = 'evasion', type = 'Integer', func = m_util.cast.factory.number('evasion'), default = 0, },   -- This is the inherent penality from the armour piece if any movement_speed = { field = 'movement_speed', type = 'Integer', func = m_util.cast.factory.number('movement_speed'), default = 0, },   -- shields block = { field = 'block', type = 'Integer', func = m_util.cast.factory.number('block'), },   -- skill gem stuff gem_description = { field = 'gem_description', type = 'Text', func = h.factory.cast_text('gem_description'), },   dexterity_percent = { field = 'dexterity_percent', type = 'Integer', func = m_util.cast.factory.percentage('dexterity_percent'), },   strength_percent = { field = 'strength_percent', type = 'Integer', func = m_util.cast.factory.percentage('strength_percent'), },   intelligence_percent = { field = 'intelligence_percent', type = 'Integer', func = m_util.cast.factory.percentage('intelligence_percent'), },   primary_attribute = { field = 'primary_attribute', type = 'String', func = function(tpl_args, frame) for _, attr in ipairs(m_game.constants.attribute_order) do               local val = tpl_args[attr .. '_percent'] if val and val >= 60 then tpl_args['primary_attribute'] = attr return end end tpl_args['primary_attribute'] = 'none' end, },   gem_tags = { field = 'gem_tags', type = 'List of String', -- TODO: default rework func = function(tpl_args, frame) if tpl_args.gem_tags then tpl_args.gem_tags = m_util.string.split(tpl_args.gem_tags, ',%s*') end end, default = function (tpl_args, frame) return {} end, },   -- 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) if tpl_args.support_gem_letter == nil then return end -- TODO replace this with a loop possibly local css_map = { strength = 'red', intelligence = 'blue', dexterity = 'green', }           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 end end 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, },   --    -- Maps --   map_tier = { field = 'tier', type = 'Integer', func = m_util.cast.factory.number('map_tier'), },   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 = m_util.cast.factory.number('map_area_level'), },   unique_map_guild_character = { field = 'unique_guild_character', type = 'String(size=1)', func_copy = function(tpl_args, frame) tpl_args.map_guild_character = tpl_args.unique_map_guild_character end, func = nil, },   unique_map_area_id = { field = 'unique_area_id', type = 'String', func = nil, -- TODO: Validate against a query? func_copy = function(tpl_args, frame) tpl_args.map_area_id = tpl_args.unique_map_area_id end, },   unique_map_area_level = { field = 'unique_area_level', type = 'Integer', func = m_util.cast.factory.number('unique_map_area_level'), func_copy = function(tpl_args, frame) tpl_args.map_area_level = tpl_args.unique_map_area_level end, },   map_series = { field = 'series', type = 'String', func = function(tpl_args, frame) if tpl_args.rarity == 'normal' and tpl_args.map_series == nil then error(string.format(i18n.errors.generic_required_parameter, 'map_series')) end end, },   -- atlas info is only for the current map series atlas_x = { field = 'x', type = 'Float', func = m_util.cast.factory.number('atlas_x'), },   atlas_y = { field = 'y', type = 'Float', func = m_util.cast.factory.number('atlas_y'), },   atlas_region_id = { field = 'region_id', type = 'String', func = nil, },   atlas_region_minimum = { field = 'region_minimum', type = 'Integer', func = m_util.cast.factory.number('atlas_region_minimum'), },   atlas_x0 = { field = 'x0', type = 'Float', func = m_util.cast.factory.number('atlas_x0'), },   atlas_x1 = { field = 'x1', type = 'Float', func = m_util.cast.factory.number('atlas_x1'), },   atlas_x2 = { field = 'x2', type = 'Float', func = m_util.cast.factory.number('atlas_x2'), },   atlas_x3 = { field = 'x3', type = 'Float', func = m_util.cast.factory.number('atlas_x3'), },   atlas_x4 = { field = 'x4', type = 'Float', func = m_util.cast.factory.number('atlas_x4'), },   atlas_y0 = { field = 'y0', type = 'Float', func = m_util.cast.factory.number('atlas_0'), },   atlas_y1 = { field = 'y1', type = 'Float', func = m_util.cast.factory.number('atlas_y1'), },   atlas_y2 = { field = 'y2', type = 'Float', func = m_util.cast.factory.number('atlas_y2'), },   atlas_y3 = { field = 'y3', type = 'Float', func = m_util.cast.factory.number('atlas_y3'), },   atlas_y4 = { field = 'y4', type = 'Float', func = m_util.cast.factory.number('atlas_y4'), },   atlas_map_tier0 = { field = 'map_tier0', type = 'Integer', func = m_util.cast.factory.number('atlas_map_tier0'), },   atlas_map_tier1 = { field = 'map_tier1', type = 'Integer', func = m_util.cast.factory.number('atlas_map_tier1'), },   atlas_map_tier2 = { field = 'map_tier2', type = 'Integer', func = m_util.cast.factory.number('atlas_map_tier2'), },   atlas_map_tier3 = { field = 'map_tier3', type = 'Integer', func = m_util.cast.factory.number('atlas_map_tier3'), },   atlas_map_tier4 = { field = 'map_tier4', type = 'Integer', func = m_util.cast.factory.number('atlas_map_tier4'), },   atlas_connections = { field = nil, type = nil, func = function(tpl_args, frame) tpl_args.atlas_connections = {} 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 tpl_args.atlas_connections[data.map2] = data table.insert(tpl_args._subobjects, data) else cont = false if i == 1 then tpl_args.atlas_connections = nil end end i = i + 1 end end, default = nil, },   --    -- Currency-like items --   stack_size = { field = 'stack_size', type = 'Integer', func = m_util.cast.factory.number('stack_size'), },   stack_size_currency_tab = { field = 'stack_size_currency_tab', type = 'Integer', func = m_util.cast.factory.number('stack_size_currency_tab'), },   description = { field = 'description', type = 'Text', func = h.factory.cast_text('description'), },   cosmetic_type = { field = 'cosmetic_type', type = 'String', func = h.factory.cast_text('cosmetic_type'), },   -- for essences is_essence = { field = nil, func = m_util.cast.factory.boolean('is_essence'), default = false, },   essence_level_restriction = { field = 'level_restriction', type = 'Integer', func = m_util.cast.factory.number('essence_level_restriction'), },   essence_level = { field = 'level', type = 'Integer', func = m_util.cast.factory.number('essence_level'), },   essence_type = { field = 'type', type = 'Integer', func = m_util.cast.factory.number('essence_type'), },   essence_category = { field = 'category', type = 'String', func = nil, },   -- blight crafting items (i.e. oils) blight_item_tier = { field = 'tier', type = 'Integer', func = m_util.cast.factory.number('blight_item_tier'), },   -- harvest seeds seed_type_id = { field = 'type_id', type = 'String', },   seed_type = { field = 'type', type = 'String', func = function (tpl_args, frame) if tpl_args.seed_type_id ~= 'none' or tpl_args.seed_type_id ~= nil then tpl_args.seed_type = m_game.seed_types[tpl_args.seed_type_id] end end },   seed_type_html = { field = nil, type = nil, func = function (tpl_args, frame) 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) end end },   seed_effect = { field = 'effect', type = 'Text', },   seed_tier = { field = 'tier', type = 'Integer', func = m_util.cast.factory.number('seed_tier'), },   seed_growth_cycles = { field = 'growth_cycles', type = 'Integer', func = m_util.cast.factory.number('seed_growth_cycles'), },   seed_required_nearby_seed_tier = { field = 'required_nearby_seed_tier', type = 'Integer', func = m_util.cast.factory.number('seed_required_nearby_seed_tier'), },   seed_required_nearby_seed_amount = { field = 'required_nearby_seed_amount', type = 'Integer', func = m_util.cast.factory.number('seed_required_nearby_seed_amount'), },   seed_consumed_wild_lifeforce_percentage = { field = 'consumed_wild_lifeforce_percentage', type = 'Integer', func = m_util.cast.factory.number('seed_consumed_wild_lifeforce_percentage'), default = 0, },   seed_consumed_vivid_lifeforce_percentage = { field = 'consumed_vivid_lifeforce_percentage', type = 'Integer', func = m_util.cast.factory.number('seed_consumed_vivid_lifeforce_percentage'), default = 0, },   seed_consumed_primal_lifeforce_percentage = { field = 'consumed_primal_lifeforce_percentage', type = 'Integer', func = m_util.cast.factory.number('seed_consumed_primal_lifeforce_percentage'), default = 0, },   seed_granted_craft_option_ids = { field = 'granted_craft_option_ids', type = 'List of String', func = m_util.cast.factory.number('seed_grandted_craft_option_ids'), default = 0, },   --    -- harvest planet boosters --   plant_booster_radius = { field = 'radius', type = 'Integer', func = m_util.cast.factory.number('plant_booster_radius'), },   plant_booster_lifeforce = { field = 'lifeforce', type = 'Integer', func = m_util.cast.factory.number('plant_booster_lifeforce'), },   plant_booster_additional_crafting_options = { field = 'additional_crafting_options', type = 'Integer', func = m_util.cast.factory.number('plant_booster_additional_crafting_options'), },   plant_booster_extra_chances = { field = 'extra_chances', type = 'Integer', func = m_util.cast.factory.number('plant_booster_extra_chances'), },   --    -- Heist properties --   heist_required_job_id = { field = 'required_job_id', type = 'String', func = h.factory.cast_text('heist_required_job_id'), },   heist_required_job_level = { field = 'required_job_level', type = 'Integer', func = m_util.cast.factory.number('heist_required_job_level'), },   heist_data = { func = function (tpl_args, frame) 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 end, },   --    -- hideout doodads (HideoutDoodads.dat) --   is_master_doodad = { field = 'is_master_doodad', type = 'Boolean', func = m_util.cast.factory.boolean('is_master_doodad'), },   master = { field = 'master', type = 'String', -- todo validate against list of master names func = m_util.cast.factory.table('master', {key='full', tbl=m_game.constants.masters}), },   master_level_requirement = { field = 'level_requirement', type = 'Integer', func = m_util.cast.factory.number('master_level_requirement'), },   master_favour_cost = { field = 'favour_cost', type = 'Integer', func = m_util.cast.factory.number('master_favour_cost'), },   variation_count = { field = 'variation_count', type = 'Integer', func = m_util.cast.factory.number('variation_count'), },   -- Propehcy prophecy_id = { field = 'prophecy_id', type = 'String', func = nil, },   prediction_text = { field = 'prediction_text', type = 'Text', func = h.factory.cast_text('prediction_text'), },   seal_cost = { field = 'seal_cost', type = 'Integer', func = m_util.cast.factory.number('seal_cost'), },   prophecy_reward = { field = 'reward', type = 'Text', func = h.factory.cast_text('prophecy_reward'), },   prophecy_objective = { field = 'objective', type = 'Text', func = h.factory.cast_text('prophecy_objective'), },   -- Divination cards card_art = { field = 'card_art', type = 'Page', func = function(tpl_args, frame) tpl_args.card_art = string.format(i18n.files.divination_card_art, tpl_args.card_art or tpl_args.name) end, },   --     -- derived stats --    -- For rarity != normal, rarity already verified base_item = { no_copy = true, field = 'base_item', type = 'String', func = function(tpl_args, frame) tpl_args.base_item = tpl_args.base_item_data['items.name'] end, },   base_item_id = { no_copy = true, field = 'base_item_id', type = 'String', func = function(tpl_args, frame) tpl_args.base_item_id = tpl_args.base_item_data['items.metadata_id'] end, },   base_item_page = { no_copy = true, field = 'base_item_page', type = 'Page', func = function(tpl_args, frame) tpl_args.base_item_page = tpl_args.base_item_data['items._pageName'] end, },   name_list = { no_copy = true, field = 'name_list', type = 'List (�) of String', func = function(tpl_args, frame) if tpl_args.name_list ~= nil then tpl_args.name_list = m_util.string.split(tpl_args.name_list, ',%s*') tpl_args.name_list[#tpl_args.name_list+1] = tpl_args.name else tpl_args.name_list = {tpl_args.name} end end, },   frame_type = { no_copy = true, field = 'frame_type', type = 'String', property = nil, func = function(tpl_args, frame) if tpl_args._flags.is_prophecy then tpl_args.frame_type = 'prophecy' return end local var = cfg.class_specifics[tpl_args.class_id] if var ~= nil and var.frame_type ~= nil then tpl_args.frame_type = var.frame_type return end if tpl_args.is_relic then tpl_args.frame_type = 'relic' return end tpl_args.frame_type = tpl_args.rarity_id end, },   --    -- args populated by mod validation --    mods = { default = function (tpl_args, frame) return {} end, 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 = { no_copy = true, field = 'physical_damage_html', type = 'Text', func = core.factory.damage_html{key='physical'}, },   fire_damage_html = { no_copy = true, field = 'fire_damage_html', type = 'Text', func = core.factory.damage_html{key='fire'}, },   cold_damage_html = { no_copy = true, field = 'cold_damage_html', type = 'Text', func = core.factory.damage_html{key='cold'}, },   lightning_damage_html = { no_copy = true, field = 'lightning_damage_html', type = 'Text', func = core.factory.damage_html{key='lightning'}, },   chaos_damage_html = { no_copy = true, field = 'chaos_damage_html', type = 'Text', func = core.factory.damage_html{key='chaos'}, },   damage_avg = { no_copy = true, field = 'damage_avg', type = 'Text', func = function(tpl_args, frame) 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 tpl_args.damage_avg = dmg end, },   damage_html = { no_copy = true, field = 'damage_html', type = 'Text', func = function(tpl_args, frame) local text = {} for _, dkey in ipairs(m_game.constants.damage_type_order) do               local value = tpl_args[dkey .. '_damage_html'] if value ~= nil then text[#text+1] = value end end if #text > 0 then tpl_args.damage_html = table.concat(text, ' ') end end, },   item_limit = { no_copy = true, field = 'item_limit', type = 'Integer', func = m_util.cast.factory.number('item_limit'), },   jewel_radius_html = { no_copy = true, field = 'radius_html', type = 'Text', func = function(tpl_args, frame) -- 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' tpl_args.jewel_radius_html = m_util.html.poe_color(color, size) end end, },   incubator_effect = { no_copy = true, field = 'effect', type = 'Text', func = nil, },   drop_areas_html = { no_copy = true, field = 'drop_areas_html', type = 'Text', func = function(tpl_args, frame) if tpl_args.drop_areas_data == nil then return end if tpl_args.drop_areas_html ~= nil then return end local areas = {} for _, data in pairs(tpl_args.drop_areas_data) do               -- skip legacy maps in the drop html listing if not string.match(data['areas.id'], '^Map.*') or string.match(data['areas.id'], '^MapWorlds.*') or string.match(data['areas.id'], '^MapAtziri.*') then areas[#areas+1] = string.format('%s', data['areas.main_page'] or data['areas._pageName'], data['areas.main_page'] or data['areas.name']) end end tpl_args.drop_areas_html = table.concat(areas, ' • ') end, },   release_version = { no_copy = true, field = 'release_version', type = 'String' },   removal_version = { no_copy = true, field = 'removal_version', type = 'String', },   --    -- args governing use of the template itself --    suppress_improper_modifiers_category = { no_copy = true, field = nil, func = m_util.cast.factory.boolean('suppress_improper_modifiers_category'), default = false, },   upgraded_from_disabled = { no_copy = true, field = nil, func = m_util.cast.factory.boolean('upgraded_from_disabled'), 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', },   },    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 = { {       name = 'physical_dps', field = 'physical_dps', damage_args = {'physical_damage', }, label_infobox = i18n.tooltips.physical_dps, html_fmt_options = { color = 'value', fmt = '%.1f', },   },    {        name = 'fire_dps', field = 'fire_dps', damage_args = {'fire_damage'}, label_infobox = i18n.tooltips.fire_dps, html_fmt_options = { color = 'fire', fmt = '%.1f', },   },    {        name = 'cold_dps', field = 'cold_dps', damage_args = {'cold_damage'}, label_infobox = i18n.tooltips.cold_dps, html_fmt_options = { color = 'cold', fmt = '%.1f', },   },    {        name = 'lightning_dps', field = 'lightning_dps', damage_args = {'lightning_damage'}, label_infobox = i18n.tooltips.lightning_dps, html_fmt_options = { color = 'lightning', fmt = '%.1f', },   },    {        name = 'chaos_dps', field = 'chaos_dps', damage_args = {'chaos_damage'}, label_infobox = i18n.tooltips.chaos_dps, html_fmt_options = { color = 'chaos', fmt = '%.1f', },   },    {        name = 'elemental_dps', field = 'elemental_dps', damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'}, label_infobox = i18n.tooltips.elemental_dps, html_fmt_options = { color = 'value', fmt = '%.1f', },   },    {        name = 'poison_dps', field = 'poison_dps', damage_args = {'physical_damage', 'chaos_damage'}, label_infobox = i18n.tooltips.poison_dps, html_fmt_options = { color = 'value', fmt = '%.1f', },   },    {        name = 'dps', field = 'dps', damage_args = {'physical_damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'}, label_infobox = i18n.tooltips.dps, html_fmt_options = { color = 'value', fmt = '%.1f', },   }, }

return core