Module:Item2/sandbox

--- -- --                               Module:Item2 -- -- This module implements Template:Item and Template:Itembox ---

-- -- TODO -- -- Items -- - -- Check if abyss jewels actually have radius modifiers -- -- Aggregate ids from skill id from modifiers -- -- -- unique items: -- 3D art -- supporter attribution -- -- Maps: -- Area level can be retrieved eventually -- -- Essence: -- type column -- monster modifier info -- -- random modifier -- -> sell price must consider random mods -- -- -- -- Item class -- -- -- -- remove the ul if name_list is not provided -- maybe smw

local getArgs = require('Module:Arguments').getArgs local m_util = require('Module:Util') local m_cargo = require('Module:Cargo') local m_skill = require('Module:Skill/sandbox') local m_area = require('Module:Area')

local m_game = mw.loadData('Module:Game')

-- Should we use the sandbox version of our submodules? local use_sandbox = m_util.misc.maybe_sandbox

-- Lazy loading local f_item_link -- require('Module:Item link').item_link local f_process_upgraded_from -- require('Module:Item2/upgrade').process_upgraded_from local f_build_cargo_data -- require('Module:Item2/cargo').build_cargo_data

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

local core = use_sandbox and require('Module:Item2/core/sandbox') or require('Module:Item2/core')

local c = {} c.item_classes = {}

-- -- Helper functions --

local h = {}

function h.debug(tpl_args, func) if tpl_args.debug == nil then return end func end

-- Lazy loading for Module:Item link function h.item_link(args) if not f_item_link then f_item_link = require('Module:Item link').item_link end return f_item_link(args) end

function h.validate_mod(tpl_args, frame, args) -- args: -- key   - implict or explicit -- i    --  value local prefix = args.key .. args.i   local value = tpl_args[prefix] local is_implicit = args.key == 'implicit' local out = { result=nil, -- commited to cargo at a later point id=nil, stat_text=nil, is_implicit=is_implicit, is_random=nil, }   local mods_table = is_implicit and tpl_args._defined_implicit_mods or tpl_args._mods if value ~= nil then out.id = value out.stat_text = tpl_args[prefix .. '_text'] out.is_random = false table.insert(mods_table, out) return true elseif tpl_args[prefix .. '_random_list'] then tpl_args._flags.random_mods = true value = m_util.string.split(tpl_args[prefix .. '_random_list'], ',%s*') for _, mod_id in ipairs(value) do           table.insert(mods_table, {                result = nil,                id = mod_id,                stat_text = tpl_args[prefix .. '_text'] or string.format(i18n.tooltips.random_mod, args.i),                is_implicit = is_implicit,                is_random = true,            }) end return true elseif tpl_args[prefix .. '_text'] then value = tpl_args[prefix .. '_text'] tpl_args._flags.text_modifier = true out.result = value out.stat_text = value out.is_random = false table.insert(mods_table, out) return true end return false end

function h.handle_range_args(tpl_args, frame, argument_key, field, value, fmt_options) fmt_options = mw.clone(fmt_options) fmt_options.return_color = true local html, colour = m_util.html.format_value(tpl_args, frame, value, fmt_options) tpl_args[argument_key .. '_html'] = html tpl_args[field .. '_html'] = html tpl_args[field .. '_range_colour'] = colour fmt_options = mw.clone(fmt_options) fmt_options.no_color = true tpl_args[field .. '_range_text'] = m_util.html.format_value(tpl_args, frame, value, fmt_options) end

h.stat = {} function h.stat.add(value, stat_cached) value.min = value.min + stat_cached.min value.max = value.max + stat_cached.max end

function h.stat.more(value, stat_cached) value.min = value.min * (1 + stat_cached.min / 100) value.max = value.max * (1 + stat_cached.max / 100) end

function h.stat.more_inverse(value, stat_cached) value.min = value.min / (1 + stat_cached.min / 100) value.max = value.max / (1 + stat_cached.max / 100) end

-- -- Processing --

function h.process_arguments(tpl_args, frame, params) for _, k in ipairs(params) do       local data = core.map[k] if data == nil then error(string.format('Invalid key or missing data for "%s"', k)) end if data.no_copy == nil then table.insert(tpl_args._base_item_args, k)       end if data.func ~= nil then data.func(tpl_args, frame) --local status, err = pcall(data.func)           -- an error was raised, return the error string instead of the template            if not status then                return err            end            -- end if tpl_args[k] == nil then if tpl_args.class_id and c.item_classes[tpl_args.class_id].defaults ~= nil and c.item_classes[tpl_args.class_id].defaults[k] ~= nil then tpl_args[k] = c.item_classes[tpl_args.class_id].defaults[k] elseif data.default ~= nil then if type(data.default) == 'function' then tpl_args[k] = data.default(tpl_args, frame) else tpl_args[k] = data.default end end end end end

-- add defaults from class specifics and class groups function h.build_item_classes(tpl_args, frame) core.map.class.func(tpl_args, frame) c.item_classes[tpl_args.class_id] = { tables = {}, args = {}, late_args = {}, defaults = {}, }   for _, table_name in ipairs(cfg.tables) do        table.insert(c.item_classes[tpl_args.class_id].tables, table_name) end

local item_classes_extend = {'tables', 'args', 'late_args'} for _, row in pairs(cfg.class_groups) do       for class, _ in pairs(row.keys) do            if class == tpl_args.class_id then for _, k in ipairs(item_classes_extend) do                   if row[k] ~= nil then for _, value in ipairs(row[k]) do                           table.insert(c.item_classes[tpl_args.class_id][k], value) end end end break end end end

local class_specifics = cfg.class_specifics[tpl_args.class_id] if class_specifics then for _, k in ipairs(item_classes_extend) do           if class_specifics[k] ~= nil then for _, value in ipairs(class_specifics[k]) do                   table.insert(c.item_classes[tpl_args.class_id][k], value) end end end if class_specifics.defaults ~= nil then for key, value in pairs(class_specifics.defaults) do               c.item_classes[tpl_args.class_id].defaults[key] = value end end end end

function h.process_mods(tpl_args, frame) -- If the item does not have its own implicit mods, fall back to the implicit mods on the base item. local implicit_mods = tpl_args._defined_implicit_mods if #implicit_mods == 0 then implicit_mods = tpl_args._base_implicit_mods end for _, v in ipairs(implicit_mods) do       table.insert(tpl_args._mods, v)    end if #tpl_args._mods > 0 then local mods = {} local mod_ids = {} local non_random_mod_ids = {} for _, mod_data in ipairs(tpl_args._mods) do           if mod_data.result == nil then mods[mod_data.id] = mod_data mod_ids[#mod_ids+1] = mod_data.id               if not mod_data.is_random then table.insert(non_random_mod_ids, mod_data.id) end end tpl_args._subobjects[#tpl_args._subobjects+1] = { _table = 'item_mods', id = mod_data.id, text = mod_data.stat_text, is_implicit = mod_data.is_implicit, is_random = mod_data.is_random, }       end local results = m_cargo.array_query{ tables={'mods'}, fields={'mods._pageName', 'mods.id', 'mods.required_level', 'mods.stat_text'}, id_field='mods.id', id_array=mod_ids, }       for _, data in ipairs(results) do            local mod_data = mods[data['mods.id']] mod_data.result = data if mod_data.is_random == false then -- update item level requirement local keys = {'required_level_final'} -- only update base item requirement if this is an implicit if mod_data.key == 'implicit' then keys[#keys+1] = 'required_level' end for _, key in ipairs(keys) do                   local req = math.floor(tonumber(data['mods.required_level']) * 0.8) if req > tpl_args[key] then tpl_args[key] = req end end end end -- fetch stats results = m_cargo.query(           {'mods', 'mod_stats'},            {'mods.id', 'mod_stats.id', 'mod_stats.min', 'mod_stats.max'},            {                join='mods._pageID=mod_stats._pageID',                where=string.format('mod_stats.id IS NOT NULL AND mods.id IN ("%s")', table.concat(mod_ids, '", "')),            }        ) for _, data in ipairs(results) do           -- Stat subobject local mod_data = mods[data['mods.id']] if mod_data.result.stats == nil then mod_data.result.stats = {data, } else mod_data.result.stats[#mod_data.result.stats+1] = data end local id = data['mod_stats.id'] local value = { min = tonumber(data['mod_stats.min']), max = tonumber(data['mod_stats.max']), }           value.avg = (value.min+value.max)/2 local prefix = '' if mod_data.is_random then prefix = '_random' end core.stats_update(tpl_args, id, value, mod_data.result['mods.id'], prefix .. '_stats') if mod_data.is_implicit then core.stats_update(tpl_args, id, value, mod_data.result['mods.id'], prefix .. '_implicit_stats') else core.stats_update(tpl_args, id, value, mod_data.result['mods.id'], prefix .. '_explicit_stats') end end if tpl_args._flags.sell_prices_override ~= true then -- fetch sell prices results = m_cargo.query(               {'mods', 'mod_sell_prices'},                {'mods.id', 'mod_sell_prices.amount', 'mod_sell_prices.name'},                {                    join='mods._pageID=mod_sell_prices._pageID',                    -- must be non random mods to avoid accumulating sell prices of randomized modifiers                    where=string.format('mod_sell_prices.amount IS NOT NULL AND mods.id IN ("%s")', table.concat(non_random_mod_ids, '", "')),                }            ) for _, data in ipairs(results) do               local mod_data = mods[data['mods.id']] if not mod_data.is_implicit then local values = { name = data['mod_sell_prices.name'], amount = tonumber(data['mod_sell_prices.amount']), }                   -- sell_prices is defined in tpl_args.sell_prices_override tpl_args.sell_prices[values.name] = (tpl_args.sell_prices[values.name] or 0) + values.amount end end end end if tpl_args._flags.sell_prices_override ~= true then local missing_sell_price = true for _, _ in pairs(tpl_args.sell_prices) do           missing_sell_price = false break end if missing_sell_price then tpl_args.sell_prices[i18n.tooltips.default_vendor_offer] = 1 end -- Set sell price on page for name, amount in pairs(tpl_args.sell_prices) do           -- sell_price_order is defined in tpl_args.sell_prices_override tpl_args.sell_price_order[#tpl_args.sell_price_order+1] = name tpl_args._subobjects[#tpl_args._subobjects+1] = { _table = 'item_sell_prices', amount = amount, name = name, }       end table.sort(tpl_args.sell_price_order) end end

function h.process_base_item(tpl_args, frame) local where if tpl_args.base_item_id ~= nil then where = string.format('items.metadata_id="%s"', tpl_args.base_item_id) elseif tpl_args.base_item_page ~= nil then where = string.format('items._pageName="%s"', tpl_args.base_item_page) elseif tpl_args.base_item ~= nil then where = string.format('items.name="%s"', tpl_args.base_item) elseif tpl_args.rarity_id ~= 'normal' then error(m_util.html.error{msg=i18n.errors.missing_base_item}) else return end if where ~= nil and tpl_args.rarity_id == 'normal' and not tpl_args._flags.is_prophecy then error(m_util.html.error{msg=i18n.errors.missing_rarity}) end where = string.format('%s AND items.class_id="%s" AND items.rarity_id="normal"', where, tpl_args.class_id) local join = {} for _, table_name in ipairs(c.item_classes[tpl_args.class_id].tables) do       if table_name ~= 'items' then join[#join+1] = string.format('items._pageID=%s._pageID', table_name) end end local fields = { 'items._pageName', 'items.name', 'items.metadata_id', }   for _, k in ipairs(tpl_args._base_item_args) do        local data = core.map[k] if data.field ~= nil then fields[#fields+1] = string.format('%s.%s', data.table, data.field) end end local result = m_cargo.query(       c.item_classes[tpl_args.class_id].tables,        fields,        {            where=where,            join=table.concat(join, ','),            groupBy='items._pageID',        }    ) if #result > 1 then error(m_util.html.error{msg=i18n.errors.duplicate_base_items}) -- TODO be more explicit in the error? elseif #result == 0 then error(m_util.html.error{msg=i18n.errors.base_item_not_found}) end result = result[1] tpl_args.base_item_data = result h.process_arguments(tpl_args, frame, {'base_item', 'base_item_page', 'base_item_id'})

--Copy values.. for _, k in ipairs(tpl_args._base_item_args) do       local data = core.map[k] if data.field ~= nil and data.func_fetch == nil then local value = result[string.format('%s.%s', data.table, data.field)] -- I can just use data.default since it will be nil if not provided (nil == nil). Neat! ;)           if value ~= nil and (tpl_args[k] == data.default or type(data.default) == 'function') then                tpl_args[k] = value                if data.func ~= nil then                    data.func(tpl_args, frame)                end                if data.func_copy ~= nil then                    data.func_copy(tpl_args, frame)                end            elseif value == nil and not data.debug_ignore_nil then                h.debug(tpl_args, function error(string.format(i18n.debug.base_item_field_not_found, data.table, data.field)) end)           elseif tpl_args[k] ~= data.default then                h.debug(tpl_args, function error(string.format(i18n.debug.field_value_mismatch, k, tostring(tpl_args[k]))) end)           end        elseif data.func_fetch ~= nil then            data.func_fetch(tpl_args, frame)        end    end end

function h.process_quest_rewards(tpl_args, frame) local rid = 1 local continue tpl_args.quest_rewards = {} tpl_args.vendor_rewards = {} repeat continue = true local prefix = string.format('quest_reward%s_', rid) local input_args = { shared = { ['type'] = true, ['quest'] = false, ['quest_id'] = false, ['act'] = true, ['class_ids'] = false, },           vendor = { ['npc'] = true, },           quest = { ['sockets'] = false, ['item_level'] = false, ['rarity_id'] = false, ['notes'] = false, },       }        local rdata = {} for key, is_required in pairs(input_args.shared) do           rdata[key] = tpl_args[prefix .. key] if is_required then if rdata[key] == nil then continue = false break end end end if rdata.quest == nil or rdata.quest_id == nil then continue = false end if continue and rdata.type == 'vendor' or rdata.type == 'quest' then for key, is_required in pairs(input_args[rdata.type]) do               rdata[key] = tpl_args[prefix .. key] if is_required then if rdata[key] == nil then continue = false break end end end else continue = false end if continue then rdata.classes = {} if rdata.class_ids ~= nil then rdata.class_ids = m_util.string.split(rdata.class_ids, ',%s*') for index, class_id in ipairs(rdata.class_ids) do                   local class = m_game.constants.characters[class_id] if class == nil then error(string.format('Class id %s is invalid', class_id)) else rdata.class_ids[index] = class.str_id rdata.classes[index] = class.name end end end if rdata.item_level then rdata.item_level = m_util.cast.number(rdata.item_level) end if rdata.rarity_id then if m_game.constants.rarities[rdata.rarity_id] == nil then error(string.format(i18n.errors.invalid_rarity_id, tostring(rdata.rarity_id))) end end rdata._table = rdata.type .. '_rewards' rdata.type = nil tpl_args[rdata._table] = rdata m_cargo.store(frame, rdata) -- TODO: Verify quests and quest ids? end rid = rid + 1 until continue == false end

-- Lazy loading for Module:Item2/cargo function h.build_cargo_data(tpl_args, frame, item_classes) if not f_build_cargo_data then f_build_cargo_data = use_sandbox and require('Module:Item2/cargo/sandbox').build_cargo_data or require('Module:Item2/cargo').build_cargo_data end return f_build_cargo_data(tpl_args, frame, item_classes) end

-- Lazy loading for Module:Item2/upgrade function h.process_upgraded_from(tpl_args, frame) if not f_process_upgraded_from then f_process_upgraded_from = use_sandbox and require('Module:Item2/upgrade/sandbox').process_upgraded_from or require('Module:Item2/upgrade').process_upgraded_from end return f_process_upgraded_from(tpl_args, frame) end

-- -- Display --

function h.strip_random_stats(tpl_args, frame, stat_text, container_type) if tpl_args._flags.random_mods and container_type == 'inline' then repeat local text = string.match(stat_text, '(.+) ') if text ~= nil then stat_text = string.gsub(stat_text, '<table class="random%-modifier%-stats.+ ', text, 1)           end        until text == nil    end    return stat_text end

function h.add_to_container_from_map(tpl_args, frame, container, mapping, container_type) local statcont = mw.html.create('span') statcont :attr('class', 'item-stats') :done local count = 0 -- Number of groups in container for _, group in ipairs(mapping) do       local lines = {} if group.func == nil then for _, line in ipairs(group) do               local show = true if container_type == 'inline' and line.inline == false then -- TODO: This is not used currently. Need to address what is hidden in inline view. show = false elseif line.show == false then show = false elseif type(line.show) == 'function' then show = line.show(tpl_args, frame, container_type) end if show then lines[#lines+1] = line.func(tpl_args, frame, container_type) end end else lines = group.func(tpl_args, frame, container_type) end if #lines > 0 then count = count + 1 local heading = '' if group.heading == nil then elseif type(group.heading) == 'function' then heading = group.heading else heading = string.format(' %s ', group.heading) end statcont :tag('span') :attr('class', 'group ' .. (group.class or '')) :wikitext(heading .. table.concat(lines, ' ')) :done end end

-- Add groups to container if count > 0 then container:node(statcont) end end

function h.make_main_container(tpl_args, frame, container_type) local container = mw.html.create('span') :attr('class', 'item-box -' .. tpl_args.frame_type) if tpl_args.class_id == 'DivinationCard' then container :tag('span') :attr('class', 'divicard-wrapper') :tag('span') :attr('class', 'divicard-art') :wikitext( 'link=|alt=' ) :done :tag('span') :attr('class', 'divicard-frame') :wikitext( '' ) :done :tag('span') :attr('class', 'divicard-header') :wikitext(tpl_args.name) :done :tag('span') :attr('class', 'divicard-stack') :wikitext(tpl_args.stack_size) :done :tag('span') :attr('class', 'divicard-reward') :tag('span') :wikitext(tpl_args.description) :done :done :tag('span') :attr('class', 'divicard-flavour text-color -flavour') :tag('span') :wikitext(tpl_args.flavour_text) :done :done :done --TODO Extras? else local header_css if tpl_args.base_item and tpl_args.rarity_id ~= 'normal' then line_type = 'double' else line_type = 'single' end local name_line = tpl_args.name if tpl_args.base_item and not tpl_args._flags.is_prophecy then name_line = name_line .. ' ' .. tpl_args.base_item end

-- Symbols - These are displayed in the item box header to indicate certain flags and/or item influences local symbols local influences = tpl_args.influences or {} if tpl_args.is_replica then symbols = {'replica', 'replica'} if #influences > 0 then symbols[2] = influences[1] end elseif #influences > 0 then symbols = {influences[1], influences[1]} if #influences > 1 then symbols[2] = influences[2] end elseif tpl_args.is_fractured then symbols = {'fractured', 'fractured'} elseif tpl_args.is_synthesised then symbols = {'synthesised', 'synthesised'} elseif tpl_args.is_veiled then symbols = {'veiled', 'veiled'} end container :tag('span') :attr( 'class', 'header -' .. line_type ) :tag('span') :attr( 'class', 'symbol' .. (symbols and ' -' .. symbols[1] or '') ) :done :wikitext(name_line) :tag('span') :attr( 'class', 'symbol' .. (symbols and ' -' .. symbols[2] or '') ) :done :done h.add_to_container_from_map(tpl_args, frame, container, c.item_infobox_groups, container_type) end if tpl_args.skill_icon ~= nil then container:wikitext(string.format('%s', tpl_args.skill_icon)) end return container end

-- -- Factory --

h.factory = {}

function h.factory.args_present(...) local args = {...} return function (tpl_args) for _, k in ipairs(args) do           if tpl_args[k] == nil then return false end end return true end end

function h.factory.display_raw_value(key) return function(tpl_args, frame) return tpl_args[key] end end

function h.factory.descriptor_value(args) -- Arguments: -- key -- tbl args = args or {} return function (tpl_args, frame, value) args.tbl = args.tbl or tpl_args if args.tbl[args.key] then value = m_util.html.abbr(value, args.tbl[args.key]) end return value end end

-- -- Additional configuration --

-- helper to loop over the range variables easier c.range_map = { min = { var = '_range_minimum', },   max = { var = '_range_maximum', },   avg = { var = '_range_average', }, }

-- -- Contents here are meant to resemble the ingame infobox of items -- c.item_infobox_groups = { -- [n]: -- class: Additional css class added to group tag -- heading: Group heading text (used for extras) -- lines: --  [n]: --   show: Show line if this function returns true; Always show if boolean true. Default: Always show --   func: Function that returns line text {       -- Cosmetic item type {           show = h.factory.args_present('cosmetic_type'), func = core.factory.infobox_line{ parts = { {                       key = 'cosmetic_type', fmt = '%s', color = 'default' },               },            },        },        -- Weapon type {           show = function(tpl_args, frame) if tpl_args.class_id == nil then return false end return cfg.class_groups.weapons.keys[tpl_args.class_id] ~= nil end, func = function(tpl_args, frame) local v = i18n.item_class_map[tpl_args.class_id] return m_util.html.format_value(tpl_args, frame, {min=v, max=v}, {color = 'default'}) end, },       -- Hideout item type {           show = function(tpl_args, frame) return tpl_args.class_id == 'HideoutDoodad' end, func = function(tpl_args, frame) return i18n.item_class_map[tpl_args.class_id] end, },       {            show = h.factory.args_present('gem_tags'), func = function(tpl_args, frame) local out = {} for i, tag in ipairs(tpl_args.gem_tags) do                   out[#out+1] = string.format(i18n.gem_tag_category, tag, tag) end return table.concat(out, ', ') end, },       {            show = h.factory.args_present('support_gem_letter_html'), func = core.factory.infobox_line{ parts = { {                       key = 'support_gem_letter_html', },               },                fmt = i18n.tooltips.support_icon, },       },        {            show = h.factory.args_present('radius'), func = core.factory.infobox_line{ parts = { {                       key = 'radius', func = h.factory.descriptor_value{key='radius_description'}, },                   {                        key = 'radius_secondary', func = h.factory.descriptor_value{key='radius_secondary_description'}, },                   {                        key = 'radius_tertiary', func = h.factory.descriptor_value{key='radius_tertiary_description'}, },               },                sep = ' / ', fmt = i18n.tooltips.radius, },       },        -- TODO: gem level here. Maybe put max level here? {           -- TODO: Rework for 3.14.0 skill cost and reservation changes show = true, func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'mana_cost', hide_default = 100, fmt = function (tpl_args, frame) if tpl_args.has_percentage_mana_cost then return '%i%%' else return '%i' end end, inline = function (tpl_args, frame) if tpl_args.has_reservation_mana_cost then return i18n.tooltips.mana_reserved else return i18n.tooltips.mana_cost end end, },                   {                        key = 'life_cost', hide_default = 100, fmt = '%i', inline = function (tpl_args, frame) return i18n.tooltips.life_cost end, },               },            },        },        {            show = true, -- TODO: Show only if has mana_multiplier func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'mana_multiplier', hide_default = 100, fmt = '%i%%', },               },                fmt = i18n.tooltips.mana_multiplier, },       },        {            show = true, -- TODO: Show only if has cooldown func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'cooldown', hide_default = 0, fmt = '%.2f ' .. m_game.units.seconds.short_lower, },               },                fmt = i18n.tooltips.cooldown_time, },       },        {            -- TODO: Combine with cooldown. Multi-use non-vaal skills display uses together with cooldown time. E.g., Cooldown Time: 8.00 sec (3 uses) show = true, func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'stored_uses', hide_default = 0, fmt = '%i', },               },                fmt = i18n.tooltips.stored_uses, },       },        {            show = true, -- TODO: Show only if has vaal_souls_requirement func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'vaal_souls_requirement', hide_default = 0, fmt = '%i', },               },                fmt = i18n.tooltips.vaal_souls_per_use, },       },        {            show = true, -- TODO: Show only if has vaal_stored_uses func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'vaal_stored_uses', hide_default = 0, fmt = '%i', },               },                fmt = i18n.tooltips.stored_uses, -- TODO: Singular or plural based on number },       },        {            show = true, -- TODO: Show only if has vaal_soul_gain_prevention_time func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'vaal_soul_gain_prevention_time', hide_default = 0, -- Technically it rounds to nearest, but it is given in milliseconds in the data, fmt = '%i ' .. m_game.units.seconds.short_lower, },               },                fmt = i18n.tooltips.vaal_soul_gain_prevention_time, },       },        {            show = h.factory.args_present('cast_time'), func = core.factory.infobox_line{ parts = { {                       key = 'cast_time', fmt = function (tpl_args, frame, value) if value.min == 0 then return i18n.tooltips.instant_cast_time end return '%.2f ' .. m_game.units.seconds.short_lower end, },               },                fmt = i18n.tooltips.cast_time, },       },        {            show = true, -- TODO: Show only if has critical_strike_chance func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'critical_strike_chance', hide_default = 0, fmt = '%.2f%%', },               },                fmt = i18n.tooltips.critical_strike_chance, },       },        {            show = true, -- TODO: Show only if has attack_speed_multiplier func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'attack_speed_multiplier', hide_default = 100, fmt = '%i%%', inline = '%s ' .. i18n.tooltips.of_base_stat, inline_color = 'value', },               },                fmt = i18n.tooltips.attack_speed_multiplier, },       },        {            show = true, -- TODO: Show only if has damage_multiplier func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'damage_multiplier', hide_default = 100, fmt = '%i%%', inline = '%s ' .. i18n.tooltips.of_base_stat, inline_color = 'value', },               },                fmt = i18n.tooltips.damage_multiplier, },       },        {            show = true, -- TODO: Show only if has damage_effectiveness func = core.factory.infobox_line{ type = 'gem', parts = { {                       key = 'damage_effectiveness', hide_default = 100, fmt = '%i%%', },               },                fmt = i18n.tooltips.damage_effectiveness, },       },        {            show = h.factory.args_present('projectile_speed'), func = core.factory.infobox_line{ parts = { {                       key = 'projectile_speed', },               },                fmt = i18n.tooltips.projectile_speed, },       },        -- Quality is before item stats, but after gem stuff and item class {           show = h.factory.args_present('quality'), func = core.factory.infobox_line{ parts = { {                       key = 'quality', fmt = '+%i%%', color = 'mod', hide_default = 0, },               },                fmt = i18n.tooltips.quality, },       },        -- Weapon only {           show = h.factory.args_present('physical_damage_html'), func = core.factory.infobox_line{ parts = { {                       key = 'physical_damage_html', fmt = '%s', },               },                fmt = i18n.tooltips.physical_damage, },              },        {            show = true, -- Elemental Damage func = function(tpl_args, frame) local keys = {'fire_damage_html', 'cold_damage_html', 'lightning_damage_html'} local elements = {} for _, key in ipairs(keys) do                   if tpl_args[key] then elements[#elements+1] = tpl_args[key] end end local text = table.concat(elements, ', ') -- returns empty string if elements is empty if text ~= '' then return string.format(i18n.tooltips.elemental_damage, text) end return end, },       {            show = h.factory.args_present('chaos_damage_html'), func = core.factory.infobox_line{ parts = { {                       key = 'chaos_damage_html', fmt = '%s', color = false, -- html already has color },               },                fmt = i18n.tooltips.chaos_damage, },              },        {            show = h.factory.args_present('critical_strike_chance_html'), func = core.factory.infobox_line{ parts = { {                       key = 'critical_strike_chance_html', fmt = '%s', },               },                fmt = i18n.tooltips.critical_strike_chance, },       },        {            show = h.factory.args_present('attack_speed_html'), func = core.factory.infobox_line{ parts = { {                       key = 'attack_speed_html', fmt = '%s', },               },                fmt = i18n.tooltips.attacks_per_second, },       },        {            show = h.factory.args_present('weapon_range_html'), func = core.factory.infobox_line{ parts = { {                       key = 'weapon_range_html', fmt = '%s', },               },                fmt = i18n.tooltips.weapon_range, },       },        -- Map only {           show = h.factory.args_present('map_area_level'), func = core.factory.infobox_line{ parts = { {                       key = 'map_area_level', fmt = '%i', },               },                fmt = i18n.tooltips.map_level, },       },        {            show = h.factory.args_present('map_tier'), func = core.factory.infobox_line{ parts = { {                       key = 'map_tier', fmt = '%i', },               },                fmt = i18n.tooltips.map_tier, },       },        {            show = function(tpl_args, frame) return tpl_args.map_guild_character ~= nil and tpl_args.rarity_id == 'normal' end, func = core.factory.infobox_line{ parts = { {                       key = 'map_guild_character', fmt = '%s', },               },                fmt = i18n.tooltips.map_guild_character, },       },        {            show = function(tpl_args, frame) return tpl_args.unique_map_guild_character ~= nil and tpl_args.rarity_id == 'unique' end, func = core.factory.infobox_line{ parts = { {                       key = 'unique_map_guild_character', fmt = '%s', },               },                fmt = i18n.tooltips.map_guild_character, },       },        {            show = true, func = core.factory.infobox_line{ type = 'stat', parts = { {                       key = 'map_item_drop_quantity_+%', fmt = '+%i%%', color = 'mod', hide_default = 0, },               },                fmt = i18n.tooltips.item_quantity, },       },        {            show = true, func = core.factory.infobox_line{ type = 'stat', parts = { {                       key = 'map_item_drop_rarity_+%', fmt = '+%i%%', color = 'mod', hide_default = 0, },               },                fmt = i18n.tooltips.item_rarity, },       },        {            show = true, func = core.factory.infobox_line{ type = 'stat', parts = { {                       key = 'map_pack_size_+%', fmt = '+%i%%', color = 'mod', hide_default = 0, },               },                fmt = i18n.tooltips.monster_pack_size, },       },        -- Jewel Only {           show = true, func = core.factory.infobox_line{ parts = { {                       key = 'item_limit', fmt = '%i', },               },                fmt = i18n.tooltips.limited_to, },       },        {            show = h.factory.args_present('jewel_radius_html'), func = core.factory.infobox_line{ parts = { {                       key = 'jewel_radius_html', fmt = '%s', color = false, -- html already has color },               },                fmt = i18n.tooltips.radius, },       },        -- Flask only {           show = h.factory.args_present('flask_mana_html', 'flask_duration_html'), --func = core.factory.display_flask('flask_mana'), func = core.factory.infobox_line{ parts = { {                       key = 'flask_mana_html', fmt = '%s', },                   {                        key = 'flask_duration_html', fmt = '%s', },               },                fmt = i18n.tooltips.flask_mana_recovery, },       },        {            show = h.factory.args_present('flask_life_html', 'flask_duration_html'), func = core.factory.infobox_line{ parts = { {                       key = 'flask_life_html', fmt = '%s', },                   {                        key = 'flask_duration_html', fmt = '%s', },               },                fmt = i18n.tooltips.flask_life_recovery, },       },        {            -- don't display for mana/life flasks show = function(tpl_args, frame) for _, k in ipairs({'flask_life_html', 'flask_mana_html'}) do                   if tpl_args[k] ~= nil then return false end end return tpl_args['flask_duration_html'] ~= nil end, func = core.factory.infobox_line{ parts = { {                       key = 'flask_duration_html', fmt = '%s', },               },                fmt = i18n.tooltips.flask_duration, },       },        {            show = h.factory.args_present('charges_per_use_html', 'charges_max_html'), func = core.factory.infobox_line{ parts = { {                       key = 'charges_per_use_html', fmt = '%s', },                   {                        key = 'charges_max_html', fmt = '%s', },               },                fmt = i18n.tooltips.flask_charges_per_use, },       },        {            show = h.factory.args_present('buff_stat_text'), func = core.factory.infobox_line{ parts = { {                       key = 'buff_stat_text', color = 'mod', },               },            },        },        -- armor {           show = h.factory.args_present('block_html'), func = core.factory.infobox_line{ parts = { {                       key = 'block_html', fmt = '%s', hide_default = 0, hide_default_key = 'block', },               },                fmt = i18n.tooltips.chance_to_block, },       },        {            show = h.factory.args_present('armour_html'), func = core.factory.infobox_line{ parts = { {                       key = 'armour_html', fmt = '%s', hide_default = 0, hide_default_key = 'armour', },               },                fmt = i18n.tooltips.armour, },       },        {            show = h.factory.args_present('evasion_html'), func = core.factory.infobox_line{ parts = { {                       key = 'evasion_html', fmt = '%s', hide_default = 0, hide_default_key = 'evasion', },               },                fmt = i18n.tooltips.evasion, },       },        {            show = h.factory.args_present('energy_shield_html'), func = core.factory.infobox_line{ parts = { {                       key = 'energy_shield_html', fmt = '%s', hide_default = 0, hide_default_key = 'energy_shield', },               },                fmt = i18n.tooltips.energy_shield, },       },        {            show = h.factory.args_present('movement_speed'), func = core.factory.infobox_line{ parts = { {                       key = 'movement_speed', fmt = '%s%%', hide_default = 0, },               },                fmt = i18n.tooltips.movement_speed, },       },        -- Amulet only {           show = h.factory.args_present('talisman_tier'), func = core.factory.infobox_line{ parts = { {                       key = 'talisman_tier', fmt = '%i', },               },                fmt = i18n.tooltips.talisman_tier, },       },        -- Misc {           show = h.factory.args_present('stack_size'), func = core.factory.infobox_line{ parts = { {                       key = 'stack_size', fmt = '%i', hide_default = 1, },               },                fmt = i18n.tooltips.stack_size, },       },        -- Essence stuff {           show = h.factory.args_present('essence_level'), func = core.factory.infobox_line{ parts = { {                       key = 'essence_level', fmt = '%i', },               },                fmt = i18n.tooltips.essence_level, },       },        -- Blight items {           show = h.factory.args_present('blight_item_tier'), func = core.factory.infobox_line{ parts = { {                       key = 'blight_item_tier', fmt = '%i', },               },                fmt = i18n.tooltips.blight_item_tier, },       },        -- Harvest seeds (upper  section) {           show = h.factory.args_present('seed_tier'), func = core.factory.infobox_line{ parts = { {                       key = 'seed_tier', fmt = '%i', },               },                fmt = i18n.tooltips.seed_tier, },       },        {            show = h.factory.args_present('seed_tier'), func = function (tpl_args, value) return i18n.tooltips.seed_monster end, },       {            show = h.factory.args_present('seed_type_html'), func = core.factory.infobox_line{ parts = { {                       key = 'seed_type_html', fmt = '%s', },               },                fmt = i18n.tooltips.seed_lifeforce_gained, },       },        {            show = h.factory.args_present('seed_growth_cycles'), func = core.factory.infobox_line{ parts = { {                       key = 'seed_growth_cycles', fmt = '%s', },               },                fmt = i18n.tooltips.seed_growth_cycles, },       },        -- Harvest plant boosters -- TODO: Remaining enhancements {           show = h.factory.args_present('plant_booster_radius'), func = core.factory.infobox_line{ parts = { {                       key = 'plant_booster_radius', fmt = '%s', },               },                fmt = i18n.tooltips.radius, },       },        {            show = h.factory.args_present('heist_required_npcs'), func = core.factory.infobox_line{ parts = { {                       key = 'heist_required_npcs', fmt = '%s', },               },                fmt = i18n.tooltips.heist_required_npc, },       },    },    -- Requirements {       -- TODO: i18n Master name? {           show = h.factory.args_present('master', 'master_level_requirement'), func = function(tpl_args, frame) -- masters have been validated before local data for i, rowdata in ipairs(m_game.constants.masters) do                   if tpl_args.master == rowdata.full then data = rowdata break end end return m_util.html.poe_color('default', i18n.tooltips.requires, string.format('%s %s', data.full, data.short_upper, tpl_args.master_level_requirement)) end },       -- Instead of item level, show drop level if any {           show = true, -- Requires... func = function(tpl_args, frame) local parts = { {                       key = 'required_level_final_html', hide_default = 1, hide_default_key = 'required_level_final', inline = i18n.tooltips.level_inline, inline_color = false, },               }                for _, attr in ipairs(m_game.constants.attribute_order) do                    parts[#parts+1] = { key = string.format('required_%s_html', attr), hide_default = 0, hide_default_key = string.format('required_%s', attr), inline = ', %s ' .. m_game.constants.attributes[attr].short_upper, inline_color = false, }               end local requirements = core.factory.infobox_line{parts = parts}(tpl_args, frame) if requirements == nil then -- return early return end requirements = string.gsub(requirements, '^, ', '') return string.format(i18n.tooltips.requires, requirements) end, },       {            show = h.factory.args_present('heist_required_job', 'heist_required_job_level'), func = core.factory.infobox_line{ parts = { {                       key = 'heist_required_job_level', fmt = '%s', },                   {                        key = 'heist_required_job', fmt = '%s', },               },                fmt = i18n.tooltips.heist_required_job, },       },    },    -- Gem description {       class = 'tc -gemdesc', {           show = h.factory.args_present('gem_description'), func = h.factory.display_raw_value('gem_description'), },   },    -- Gem Quality Stats {       class = 'tc -mod', {           show = h.factory.args_present('skill_quality'), func = function(tpl_args, frame) local span = mw.html.create('span') span :addClass('quality') local span2 = span:tag('span') span2 :addClass('quality-header') :tag('span') :wikitext(m_util.html.poe_color('default', i18n.tooltips.gem_quality)) for i, quality_data in ipairs(tpl_args.skill_quality) do                   local span_inner = span2:tag('span') span_inner :addClass('quality-section-select') :addClass('quality-' .. i)                       :wikitext(i) :tag('span') :wikitext(i18n.tooltips['gem_quality_' .. i]) if i == 1 then span_inner:addClass('quality-selected') end end for i, quality_data in ipairs(tpl_args.skill_quality) do                   local span_inner = span:tag('span') span_inner :addClass('quality-box') :addClass('quality-' .. i)                       :wikitext(quality_data.stat_text) if i == 1 then span_inner:addClass('quality-selected') end end return tostring(span) end, },   },    -- Gem Implicit Stats {       class = 'tc -mod', {           show = function(tpl_args, frame) return cfg.class_groups.gems.keys[tpl_args.class_id] and tpl_args.stat_text end, func = function(tpl_args, frame) lines = {} lines[#lines+1] = tpl_args.stat_text for _, tag in ipairs(tpl_args.gem_tags) do                   if tag == m_game.constants.item.gem_tags.vaal.tag then lines[#lines+1] = i18n.tooltips.corrupted break end end return table.concat(lines, ' ') end, },   },    -- Implicit Stats {       class = 'tc -mod', func = function(tpl_args, frame, container_type) if tpl_args.implicit_stat_text then return {h.strip_random_stats(tpl_args, frame, tpl_args.implicit_stat_text, container_type)} else return {} end end, },   -- Stats {       class = 'tc -mod', func = function(tpl_args, frame, container_type) if tpl_args.explicit_stat_text then return {h.strip_random_stats(tpl_args, frame, tpl_args.explicit_stat_text, container_type)} else return {} end end, },   -- Experience --{       {            show = h.factory.args_present('experience'),            func = core.factory.infobox_line{                parts = {                    {                        key = 'experience',                        fmt = '%i',                    },                },            },        },    },-- -- Harvest seeds (lower section) {       class = 'tc -mod', {           show = h.factory.args_present('seed_consumed_wild_lifeforce_percentage'), func = function (tpl_args, frame) if tpl_args.seed_consumed_wild_lifeforce_percentage > 0 then return string.format(i18n.tooltips.seed_lifeforce_consumed, tpl_args.seed_consumed_wild_lifeforce_percentage, m_util.html.poe_color('wild', m_game.seed_types.wild)) end end },       {            show = h.factory.args_present('seed_consumed_vivid_lifeforce_percentage'), func = function (tpl_args, frame) if tpl_args.seed_consumed_vivid_lifeforce_percentage > 0 then return string.format(i18n.tooltips.seed_lifeforce_consumed, tpl_args.seed_consumed_vivid_lifeforce_percentage, m_util.html.poe_color('vivid', m_game.seed_types.vivid)) end end },       {            show = h.factory.args_present('seed_consumed_primal_lifeforce_percentage'), func = function (tpl_args, frame) if tpl_args.seed_consumed_primal_lifeforce_percentage > 0 then return string.format(i18n.tooltips.seed_lifeforce_consumed, tpl_args.seed_consumed_primal_lifeforce_percentage, m_util.html.poe_color('primal', m_game.seed_types.primal)) end end },       {            show = h.factory.args_present('seed_required_nearby_seed_tier', 'seed_type_html', 'seed_required_nearby_seed_amount'), func = core.factory.infobox_line{ parts = { {                       key = 'seed_required_nearby_seed_amount', fmt = '%s', },                   {                        key = 'seed_type_html', fmt = '%s', },                   {                        key = 'seed_required_nearby_seed_tier', fmt = '%s', },               },                fmt = i18n.tooltips.seed_required_seeds, color = 'mod', },       },    },    {        class = 'tc -crafted', {           show = h.factory.args_present('seed_effect'), func = h.factory.display_raw_value('seed_effect'), },   },    -- Description (currency, doodads) {       class = 'tc -mod', {           show = h.factory.args_present('description'), func = h.factory.display_raw_value('description'), },       --{            show = h.factory.args_present('plant_booster_additional_crafting_options'),            func = core.factory.infobox_line{                parts = {                    {                        key = 'plant_booster_additional_crafting_options',                        fmt = '%s',                    },                },                fmt = i18n.tooltips.plant_booster_additional_crafting_options,            },        },        {            show = h.factory.args_present('plant_booster_extra_chances'),            func = core.factory.infobox_line{                parts = {                    {                        key = 'plant_booster_extra_chances',                        fmt = '%s%%',                    },                },                fmt = i18n.tooltips.plant_booster_extra_chances,            },        },        {            show = h.factory.args_present('plant_booster_lifeforce'),            func = core.factory.infobox_line{                parts = {                    {                        key = 'plant_booster_lifeforce',                        fmt = '%s%%',                    },                },                fmt = i18n.tooltips.plant_booster_lifeforce,            },        }, },   {        class = 'tc -crafted', {           show = h.factory.args_present('incubator_effect'), func = h.factory.display_raw_value('incubator_effect'), },   },    -- Variations (for doodads) {       class = 'tc -mod', {           show = h.factory.args_present('variation_count'), func = function(tpl_args, frame) local txt if tpl_args.variation_count == 1 then txt = i18n.tooltips.variation_singular else txt = i18n.tooltips.variation_plural end return string.format('%i %s', tpl_args.variation_count, txt) end, },   },    -- Flavour Text {       class = 'tc -flavour', {           show = h.factory.args_present('flavour_text'), func = h.factory.display_raw_value('flavour_text'), },   },    -- Prophecy text {       class = 'tc -value', {           show = h.factory.args_present('prediction_text'), func = h.factory.display_raw_value('prediction_text'), },   },    -- Can not be traded or modified {       class = 'tc -canttradeormodify', {           show = h.factory.args_present('cannot_be_traded_or_modified'), func = function(tpl_args, frame) if tpl_args.cannot_be_traded_or_modified == true then return i18n.tooltips.cannot_be_traded_or_modified end end, },   },    -- Help text {       class = 'tc -help', {           show = h.factory.args_present('help_text'), func = h.factory.display_raw_value('help_text'), },   },    -- Cost (i.e. vendor costs) {       --class = '', {           show = h.factory.args_present('master_favour_cost'), func = core.factory.infobox_line{ parts = { {                       key = 'master_favour_cost', color = 'currency', },               },                fmt = i18n.tooltips.favour_cost, },       },        {            show = h.factory.args_present('seal_cost'), func = core.factory.infobox_line{ parts = { {                       key = 'seal_cost', fmt = function (tpl_args, frame, value) return '%dx ' .. h.item_link{metadata_id='Metadata/Items/Currency/CurrencySilverCoin', html=''} end, color = 'currency', },               },                fmt = i18n.tooltips.seal_cost, },       },    }, }

-- -- This is meant to show additional information about the item in a separate infobox -- c.extra_display_groups = { {       heading = i18n.tooltips.extra_info, class = '', {           show = h.factory.args_present('atlas_connections'), func = function(tpl_args, frame) local fields = { [false] = { value = '✗', sort = 0, class = 'table-cell-xmark', },                   [true] = { value = '✓', sort = 1, class = 'table-cell-checkmark', },               }                local tbl = mw.html.create('table') tbl :attr('class', 'wikitable') :attr('style', 'width:100%;') :tag('tr') :tag('th') :attr('colspan', 6) :attr('style', 'text-decoration: underline;') :wikitext(i18n.tooltips.header_overall) :done :done :tag('tr') :tag('th') :wikitext(i18n.tooltips.header_upgrades) :done :tag('th') :wikitext(0) :done :tag('th') :wikitext(1) :done :tag('th') :wikitext(2) :done :tag('th') :wikitext(3) :done :tag('th') :wikitext(4) :done :done for _, vtype in ipairs({'tier', 'level'}) do                   local tr = tbl:tag('tr') tr                       :tag('th') :wikitext(i18n.tooltips['header_map_' .. vtype]) :done for i=0,4 do                       local value = tpl_args['atlas_map_tier' .. i]                       if value == 0 then value = fields[false].value elseif vtype == 'level' then value = value + 67 end tr                           :tag('td') :wikitext(value) :done end tr:done end tbl :tag('tr') :tag('th') :attr('colspan', 6) :attr('style', 'text-decoration: underline;') :wikitext(i18n.tooltips.header_connections) :done :done -- sort alphabetically local sorted = {} for key, value in pairs(tpl_args.atlas_connections) do                   sorted[#sorted+1] = key end table.sort(sorted) for _, key in ipairs(sorted) do                   local tr = tbl:tag('tr') tr                       :tag('th') :wikitext(key) :done for i=0,4 do                       local field = fields[tpl_args.atlas_connections[key]['region' .. i]] tr                           :tag('td') :attr('data-sort-value', field.sort) :attr('class', field.class) :wikitext(field.value) :done end tr:done end return tostring(tbl) end },   },    -- Drop info {       heading = i18n.tooltips.drop_restrictions, class = '', {           show = h.factory.args_present('drop_enabled'), func = core.factory.infobox_line{ parts = { {                       key = 'drop_level', fmt = '%i', },                   {                        key = 'drop_level_maximum', fmt = '%i', hide_default = 100, },               },                sep = ' / ', fmt = i18n.tooltips.level, },       },        {            show = h.factory.args_present('drop_leagues'), func = function(tpl_args, frame) return string.format(i18n.tooltips.league_restriction, m_util.html.poe_color('value', table.concat(tpl_args.drop_leagues, ', '))) end },       {            show = h.factory.args_present('drop_areas_html'), func = h.factory.display_raw_value('drop_areas_html'), },       {            show = h.factory.args_present('drop_text'), func = h.factory.display_raw_value('drop_text'), },       {            show = function(tpl_args, frame) if tpl_args.drop_enabled == true then return false end return true end, func = function(tpl_args, frame) local span = mw.html.create('span') span :attr('class', 'infobox-disabled-drop') :wikitext(i18n.tooltips.drop_disabled) :done return tostring(span) end, },   },    {        heading = i18n.tooltips.purchase_costs, {           show = function(tpl_args, frame) for rarity, data in pairs(tpl_args.purchase_costs) do                   if #data > 0 then return true end end return false end, func = function(tpl_args, frame) local tbl = mw.html.create('table') tbl --:attr('class', 'wikitable') :attr('style', 'width: 100%; margin-top: 0px;') for _, rarity_id in ipairs(m_game.constants.rarity_order) do                   local data = tpl_args.purchase_costs[rarity_id] if #data > 0 then local tr = tbl:tag('tr') tr                           :tag('td') :wikitext(m_game.constants.rarities[rarity_id].long_upper) local td = tr:tag('td') for _, purchase_data in ipairs(data) do                           td:wikitext(string.format('%dx %s ', purchase_data.amount, purchase_data.name)) end end end return tostring(tbl) end, },   },    {        heading = i18n.tooltips.sell_price, {           show = h.factory.args_present('sell_price_order'), func = function(tpl_args, frame) local out = {} for _, item_name in ipairs(tpl_args.sell_price_order) do                   out[#out+1] = string.format('%dx %s', tpl_args.sell_prices[item_name], item_name) end return table.concat(out, ' ') end, },   },    -- Damage per second {       heading = i18n.tooltips.damage_per_second, -- Autoinsert here from dps map },   {        heading = i18n.tooltips.misc, {           show = h.factory.args_present('class'), func = core.factory.infobox_line{ parts = { {                       key = 'class', fmt = '%s', },               },                fmt = i18n.tooltips.item_class, },       },        {            show = h.factory.args_present('metadata_id'), func = core.factory.infobox_line{ parts = { {                       key = 'metadata_id', fmt = '%s', truncate = true, },               },                fmt = i18n.tooltips.metadata_id, },       },    }, }

for i, data in ipairs(core.dps_map) do   table.insert(c.extra_display_groups[4], {        show = h.factory.args_present(data.name .. '_html'),       func = core.factory.infobox_line{            parts = {                {                    key = data.name .. '_html',                    inline = data.label_infobox .. ': %s',                    fmt = '%s',                    -- the html already contains the colour                    color = false,                },            },        },    }) if i == 5 then table.insert(c.extra_display_groups[4], {           show = function (tpl_args, frame)                 return tpl_args.elemental_dps_html ~= nil or tpl_args.poison_dps_html ~= nil            end,            func = function (tpl_args, frame)                return ''            end,        }) elseif i == 7 then table.insert(c.extra_display_groups[4], {           show = h.factory.args_present('dps_html'),            func = function (tpl_args, frame)                return ''            end,        }) end end

-- -- Exported functions --

local p = {}

-- -- Template:Item -- function p.item(frame) local t = os.clock local tpl_args = getArgs(frame, {       parentFirst = true    }) frame = m_util.misc.get_frame(frame) --   -- Shared args --   tpl_args._flags = {} tpl_args._base_item_args = {} tpl_args._base_implicit_mods = {} tpl_args._defined_implicit_mods = {} tpl_args._mods = {} for _, k in ipairs({, '_random'}) do       for _, prefix in ipairs({, '_implicit', '_explicit'}) do            tpl_args[k .. prefix .. '_stats'] = {} end end tpl_args._subobjects = {} tpl_args._properties = {} tpl_args._errors = {} h.process_arguments(tpl_args, frame, {'class_id'}) h.build_item_classes(tpl_args, frame) h.build_cargo_data(tpl_args, frame, c.item_classes) -- Using general purpose function to handle release and removal versions m_util.args.version(tpl_args, {frame=frame, set_properties=true}) -- Must validate some argument early. It is required for future things h.process_arguments(tpl_args, frame, cfg.default_args) h.process_arguments(tpl_args, frame, c.item_classes[tpl_args.class_id].args) -- Base Item h.process_base_item(tpl_args, frame) -- Prophecy special snowflake if tpl_args._flags.is_prophecy then err = h.process_arguments(tpl_args, frame, cfg.prophecy_args) if err then return err end end -- Mods for _, k in ipairs({'implicit', 'explicit'}) do       local success = true local i = 1 while success do           success = h.validate_mod(tpl_args, frame, {key=k, i=i}) i = i + 1 end end h.process_mods(tpl_args, frame) -- Add stats - this is for when mods are not set, but we still need stats to calcuate new armour values etc m_util.args.stats(tpl_args, {prefix='extra_'}) for _, stat in ipairs(tpl_args.extra_stats) do       if stat.value ~= nil then stat.min = stat.value stat.max = stat.value stat.avg = stat.value end core.stats_update(tpl_args, stat.id, stat, nil, '_stats') core.stats_update(tpl_args, stat.id, stat, nil, '_explicit_stats') end

-- Transpose stats into cargo data for _, random_prefix in ipairs({, '_random'}) do       for _, type_prefix in ipairs({, '_implicit', '_explicit'}) do            for id, data in pairs(tpl_args[random_prefix .. type_prefix .. '_stats']) do                local is_implicit if type_prefix == '_implicit' then is_implicit = true elseif type_prefix == '_explicit' then is_implicit = false end tpl_args._subobjects[#tpl_args._subobjects+1] = { _table = 'item_stats', id = id, min = data.min, max = data.max, avg = data.avg, is_implicit = is_implicit, is_random = random_prefix == '_random', }           end end end -- Handle extra stats (for gems) if cfg.class_groups.gems.keys[tpl_args.class_id] then m_skill._skill(tpl_args, frame) end --   -- Handle local stats increases/reductions/additions --   local skip = {} -- general stats for k, data in pairs(core.stat_map) do       local value = tpl_args[k] if value ~= nil and skip[k] == nil then value = {min=value, max=value, base=value} -- If stats are overriden we scan save some CPU time here local overridden = false if data.stats_override ~= nil then for stat_id, override_value in pairs(data.stats_override) do                   local stat_value = tpl_args._stats[stat_id] if stat_value ~= nil then -- Use the value of stat if override_value == true then value.min = stat_value.min value.max = stat_value.max overridden = true elseif stat_value ~= 0 then value.min = override_value.min value.max = override_value.max overridden = true end end end end if overridden == false then -- The simple cases; this must be using ipairs as "add" must apply before for _, operator in ipairs({'add', 'more'}) do                   local st = data['stats_' .. operator] if st ~= nil then for _, statid in ipairs(st) do                           if tpl_args._stats[statid] ~= nil then h.stat[operator](value, tpl_args._stats[statid]) end end end end -- For increased stats we need to add them up first for stat_key, stat_func in pairs({increased=h.stat.more, increased_inverse=h.stat.more_inverse}) do                   local st = data['stats_' .. stat_key] if st ~= nil then local total_increase = {min=0, max=0} for _, statid in ipairs(st) do                           if tpl_args._stats[statid] ~= nil then for var, current_value in pairs(total_increase) do                                   total_increase[var] = current_value + tpl_args._stats[statid][var] end end end stat_func(value, total_increase) end end if data.minimum ~= nil then for _, key in ipairs({'min', 'max'}) do                       if value[key] < data.minimum then value[key] = data.minimum end end end else

end value.avg = (value.min + value.max) / 2 -- don't add the properties unless we need to           if (data.default ~= nil and (value.min ~= data.default or value.max ~= data.default)) or data.default == nil then for short_key, range_data in pairs(c.range_map) do                   tpl_args[data.field .. range_data.var] = value[short_key] end -- process to HTML to use on list pages or other purposes h.handle_range_args(tpl_args, frame, k, data.field, value, data.html_fmt_options or {}) end for short_key, range_data in pairs(c.range_map) do               tpl_args[k .. range_data.var] = value[short_key] end end end

-- calculate and handle weapon dps if cfg.class_groups.weapons.keys[tpl_args.class_id] then for _, data in ipairs(core.dps_map) do           local damage = { min = {}, max = {}, }           for var_type, value in pairs(damage) do                -- covers the min/max/avg range for short_key, range_data in pairs(c.range_map) do                   value[short_key] = 0 for _, damage_key in ipairs(data.damage_args) do                       value[short_key] = value[short_key] + (tpl_args[string.format('%s_%s%s', damage_key, var_type, range_data.var)] or 0) end end end

local value = {} for short_key, range_data in pairs(c.range_map) do               local result = (damage.min[short_key] + damage.max[short_key]) / 2 * tpl_args[string.format('attack_speed%s', range_data.var)] value[short_key] = result tpl_args[string.format('%s%s', data.field, range_data.var)] = result end if value.avg > 0 then h.handle_range_args(tpl_args, frame, data.name, data.field, value, data.html_fmt_options or {}) end end end -- late processing h.process_arguments(tpl_args, frame, cfg.late_args) -- General late args h.process_arguments(tpl_args, frame, c.item_classes[tpl_args.class_id].late_args) -- Class-specific late args -- Handle upgrade from restrictions/info h.process_upgraded_from(tpl_args, frame) -- Quest reward info h.process_quest_rewards(tpl_args, frame) --    -- Infobox handling --    -- Store the infobox so it can be accessed with ease on other pages tpl_args.html = tostring(h.make_main_container(tpl_args, frame, 'inline')) local container = h.make_main_container(tpl_args, frame, 'infobox') if tpl_args.inventory_icon ~= nil and tpl_args.class_id ~= 'DivinationCard' then container:wikitext(string.format('%sx%spx', tpl_args.inventory_icon, cfg.image_size_full*tpl_args.size_x, cfg.image_size_full*tpl_args.size_y)) end --   -- Secondary infobox --   tpl_args.extra_infobox = mw.html.create('span') :attr( 'class', 'item-box -' .. tpl_args.frame_type) h.add_to_container_from_map(tpl_args, frame, tpl_args.extra_infobox, c.extra_display_groups) --    -- Output --   local infobox = mw.html.create('span') infobox :attr('class', 'infobox-page-container') :node(container) :node(tpl_args.extra_infobox) -- skill_screenshot is set in skill module if tpl_args.skill_screenshot then infobox:wikitext(string.format(' 300px', tpl_args.skill_screenshot)) end local out = tostring(infobox) tpl_args.html_extra = out --    -- Category handling --    local cats = {} if tpl_args.rarity_id == 'unique' then cats[#cats+1] = string.format(i18n.categories.unique_affix, tpl_args.class) elseif tpl_args._flags.is_prophecy then cats[#cats+1] = i18n.categories.prophecies elseif tpl_args._flags.is_blight_item then cats[#cats+1] = i18n.categories.blight_item elseif tpl_args.is_talisman then cats[#cats+1] = i18n.categories.talismans elseif tpl_args.is_essence then cats[#cats+1] = i18n.categories.essences elseif tpl_args.class_id == 'Map' then cats[#cats+1] = string.format('%s %s', tpl_args.map_series, tpl_args.class) else cats[#cats+1] = tpl_args.class end if tpl_args.rarity_id ~= 'normal' or tpl_args.base_item_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy' then cats[#cats+1] = i18n.categories.derived_items else cats[#cats+1] = i18n.categories.base_items end for _, attr in ipairs(m_game.constants.attribute_order) do       if tpl_args[attr .. '_percent'] then cats[#cats+1] = string.format('%s %s', m_game.constants.attributes[attr].long_upper, tpl_args.class) end end local affix if tpl_args.class_id == 'Active Skill Gem' or tpl_args.class_id == 'Support Skill Gem' then affix = i18n.categories.gem_tag_affix end if affix ~= nil then for _, tag in ipairs(tpl_args.gem_tags) do           cats[#cats+1] = string.format(affix, tag) end end

if #tpl_args._defined_implicit_mods > 0 and tpl_args.rarity_id ~= 'normal' then cats[#cats+1] = i18n.categories.implicit_modifier_override end if #tpl_args.alternate_art_inventory_icons > 0 then cats[#cats+1] = i18n.categories.alternate_artwork end -- TODO: add maintenance categories if tpl_args.release_version == nil then cats[#cats+1] = i18n.categories.missing_release_version end if tpl_args._flags.text_modifier and not tpl_args.suppress_improper_modifiers_category then cats[#cats+1] = i18n.categories.improper_modifiers end --    for _, k in ipairs({'broken_upgraded_from_reference', 'duplicate_upgraded_from_reference', 'duplicate_query_area_ids', 'sell_prices_override'}) do        if tpl_args._flags[k] then cats[#cats+1] = i18n.categories[k] end end out = out .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug}) --   --  Misc --   -- Also show the infobox for areas right away for maps, since they're both on the same page -- if tpl_args.map_series ~= i18n.misc.betrayal then local query_id if tpl_args.rarity_id == 'normal' and tpl_args.map_area_id ~= nil then query_id = tpl_args.map_area_id elseif tpl_args.rarity_id == 'unique' and tpl_args.unique_map_area_id ~= nil then query_id = tpl_args.unique_map_area_id end

if query_id then out = out .. m_area.query_area_info{cats=yes, where=string.format('areas.id="%s"', query_id)} end --    -- Store cargo data --    -- Map argument values for cargo storage for _, table_name in ipairs(c.item_classes[tpl_args.class_id].tables) do       tpl_args._subobjects[table_name] = { _table = table_name, }   end for k, v in pairs(tpl_args) do       local data = core.map[k] if data ~= nil then if data.table ~= nil and data.field ~= nil then if data.type == 'Integer' then v = tonumber(string.format("%.0f", v)) if v ~= tpl_args[k] then mw.logObject(string.format('Float value "%s" for integer field "%s.%s"', tpl_args[k], data.table, data.field)) end end tpl_args._subobjects[data.table][data.field] = v           elseif data.table == nil and data.field ~= nil then error(string.format('Missing table for field "%s", key "%s", \nvalue:\n "%s" \ndata:\n%s', data.field, k, mw.dumpObject(v), mw.dumpObject(data))) elseif data.table ~= nil and data.field == nil then error(string.format('Missing field for table "%s", key "%s", \nvalue:\n "%s" \ndata:\n%s', data.table, k, mw.dumpObject(v), mw.dumpObject(data))) end end end -- Don't actually save data in testing mode if not tpl_args.test then local attach = {} for _, data in pairs(tpl_args._subobjects) do           if attach[data._table] == nil then local i = 0 for _, _ in pairs(data) do                   i = i + 1 -- Don't attach to tables we don't store data to. _table is always present so we need to check for 2 or more entries. if i > 1 then attach[data._table] = true -- Keeping this here in case it might be useful in the future. -- Recreating table is very slow and doing anything while the queue is running just creates a big mess. frame:expandTemplate{title=string.format('Template:Item/cargo/attach/%s', data._table), args={}} break end end end m_cargo.store(frame, data, {               debug=tpl_args.debug,                sep={                    name_list='�',                },            }) end end -- Show additional error messages in console to help fixing them if #tpl_args._errors > 0 then mw.logObject(table.concat(tpl_args._errors, '\n')) end if tpl_args.test then tpl_args.out = out return tpl_args else mw.logObject(os.clock - t)       return out end end

p.itembox = p.item -- Apparently this is still used somewhere???

-- -- Template:Itembox -- function p.query_itembox(frame) --   Queries for an item box.    Examples    =p.query_itembox{page='Beach Map (Betrayal)'} local tpl_args = getArgs(frame, {       parentFirst = true    }) frame = m_util.misc.get_frame(frame) local results = m_cargo.query(       {'items', 'maps', 'areas'},         {'items.html_extra', 'maps.area_id', 'areas.id', 'areas.infobox_html'},        {            join='items._pageID=maps._pageID, maps.area_id=areas.id',            where=string.format('items._pageName="%s"', tpl_args.page),        }    ) if #results == 0 then error(i18n.errors.no_results_found) end local out = { results[1]['items.html_extra'], results[1]['areas.infobox_html'], }   return table.concat(out, '') end

return p