Path of Exile Wiki

Подумайте над тем, чтобы помочь в обновлении Wiki.

ПОДРОБНЕЕ

Path of Exile Wiki
Path of Exile Wiki
50 394
страницы
(Отмена правки 494937, сделанной Ruba159753 (обсуждение))
Метка: отмена
(Infobox formatting: Acquisition "Level" -> "Area level"; Hideout decoration master and favour cost; Prophecy seal cost; "Miscellaneous" -> "Metadata")
 
(не показано 14 промежуточных версий этого же участника)
Строка 35: Строка 35:
 
--
 
--
 
-- remove the ul if name_list is not provided
 
-- remove the ul if name_list is not provided
-- maybe smw
 
 
   
  +
require('Module:No globals')
 
local getArgs = require('Module:Arguments').getArgs
 
local getArgs = require('Module:Arguments').getArgs
 
local m_util = require('Module:Util')
 
local m_util = require('Module:Util')
 
local m_cargo = require('Module:Cargo')
 
local m_cargo = require('Module:Cargo')
local m_skill = require('Module:Skill')
 
local m_area = require('Module:Area')
 
   
 
local m_game = mw.loadData('Module:Game')
 
local m_game = mw.loadData('Module:Game')
   
 
-- Should we use the sandbox version of our submodules?
 
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox()
+
local use_sandbox = m_util.misc.maybe_sandbox('Item2')
   
 
-- Lazy loading
 
-- Lazy loading
  +
local f_skill -- require('Module:Skill')._skill
 
local f_item_link -- require('Module:Item link').item_link
 
local f_item_link -- require('Module:Item link').item_link
  +
local f_query_area_info -- require('Module:Area').query_area_info
 
local f_process_upgraded_from -- require('Module:Item2/upgrade').process_upgraded_from
 
local f_process_upgraded_from -- require('Module:Item2/upgrade').process_upgraded_from
local f_build_cargo_data -- require('Module:Item2/cargo').build_cargo_data
+
local f_append_schema -- require('Module:Item2/cargo').append_schema
   
 
-- The cfg table contains all localisable strings and configuration, to make it
 
-- The cfg table contains all localisable strings and configuration, to make it
Строка 62: Строка 61:
 
local core = use_sandbox and require('Module:Item2/core/sandbox') or require('Module:Item2/core')
 
local core = use_sandbox and require('Module:Item2/core/sandbox') or require('Module:Item2/core')
   
  +
-- Declare early to avoid errors
 
local c = {}
 
local c = {}
c.item_classes = {}
 
   
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
Строка 71: Строка 70:
 
local h = {}
 
local h = {}
   
  +
-- Lazy loading for Module:Skill
function h.debug(tpl_args, func)
 
if tpl_args.debug == nil then
+
function h.skill(tpl_args, frame)
return
+
if not f_skill then
  +
f_skill = use_sandbox and require('Module:Skill/sandbox')._skill or require('Module:Skill')._skill
 
end
 
end
  +
return f_skill(tpl_args, frame)
func()
 
 
end
 
end
   
Строка 84: Строка 84:
 
end
 
end
 
return f_item_link(args)
 
return f_item_link(args)
  +
end
  +
  +
-- Lazy loading for Module:Item link
  +
function h.query_area_info(args)
  +
if not f_query_area_info then
  +
f_query_area_info = require('Module:Area').query_area_info
  +
end
  +
return f_query_area_info(args)
  +
end
  +
  +
-- Lazy loading for Module:Item2/cargo
  +
function h.append_schema(tpl_args, frame, tables)
  +
if not f_append_schema then
  +
f_append_schema = use_sandbox and require('Module:Item2/cargo/sandbox').append_schema or require('Module:Item2/cargo').append_schema
  +
end
  +
return f_append_schema(tpl_args, frame, tables)
  +
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
 
end
   
Строка 173: Строка 197:
   
 
function h.process_arguments(tpl_args, frame, params)
 
function h.process_arguments(tpl_args, frame, params)
  +
tpl_args._processed_args = tpl_args._processed_args or {}
 
for _, k in ipairs(params) do
 
for _, k in ipairs(params) do
 
local data = core.map[k]
 
local data = core.map[k]
 
if data == nil then
 
if data == nil then
  +
if tpl_args.debug then
error(string.format('Invalid key or missing data for "%s"', k))
 
  +
error(string.format(i18n.debug.invalid_argument_key, 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
]]--
+
else
  +
if data.inherit ~= false then
end
 
  +
table.insert(tpl_args._base_item_args, k)
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
+
if data.func ~= nil then
  +
tpl_args[k] = data.func(tpl_args, frame, tpl_args[k])
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
+
if tpl_args[k] == nil then
  +
if tpl_args.class_id and tpl_args._item_config.defaults ~= nil and tpl_args._item_config.defaults[k] ~= nil then
end
 
  +
-- Item defaults
 
local class_specifics = cfg.class_specifics[tpl_args.class_id]
+
tpl_args[k] = tpl_args._item_config.defaults[k]
if class_specifics then
+
elseif data.default ~= nil then
  +
-- Generic defaults
for _, k in ipairs(item_classes_extend) do
 
if class_specifics[k] ~= nil then
+
if type(data.default) == 'function' then
for _, value in ipairs(class_specifics[k]) do
+
tpl_args[k] = data.default(tpl_args, frame)
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']) * cfg.item_required_level_modifier_contribution)
 
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
 
else
rdata.class_ids[index] = class.str_id
+
tpl_args[k] = data.default
rdata.classes[index] = class.name
 
 
end
 
end
 
end
 
end
 
end
 
end
+
tpl_args._processed_args[k] = true
 
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
 
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
 
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
 
end
   
Строка 709: Строка 333:
 
else
 
else
 
local header_css
 
local header_css
  +
local line_type
 
if tpl_args.base_item and tpl_args.rarity_id ~= 'normal' then
 
if tpl_args.base_item and tpl_args.rarity_id ~= 'normal' then
 
line_type = 'double'
 
line_type = 'double'
Строка 769: Строка 394:
 
h.factory = {}
 
h.factory = {}
   
function h.factory.args_present(...)
+
function h.factory.maybe_show_infobox_line(args)
  +
-- Verifies that a list of keys are present in tpl_args and that the item
local args = {...}
 
  +
-- should use those keys. Returns true if all conditions are met, otherwise
  +
-- returns false.
 
return function (tpl_args)
 
return function (tpl_args)
for _, k in ipairs(args) do
+
local keys = args.keys or {}
  +
local conditions = args.conditions or {}
  +
if #keys == 0 then
  +
return false
  +
end
  +
for _, k in ipairs(keys) do
 
if tpl_args[k] == nil then
 
if tpl_args[k] == nil then
  +
return false
  +
end
  +
local t = type(tpl_args[k])
  +
if (t == 'table' or t == 'string') and #tpl_args[k] == 0 then
  +
return false
  +
end
  +
if conditions.processed == true and tpl_args._processed_args[k] == nil then
 
return false
 
return false
 
end
 
end
Строка 832: Строка 471:
 
-- Cosmetic item type
 
-- Cosmetic item type
 
{
 
{
show = h.factory.args_present('cosmetic_type'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'cosmetic_type'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 845: Строка 487:
 
-- Weapon type
 
-- Weapon type
 
{
 
{
show = function(tpl_args, frame)
+
show = function (tpl_args, frame)
 
if tpl_args.class_id == nil then
 
if tpl_args.class_id == nil then
 
return false
 
return false
 
end
 
end
 
 
return cfg.class_groups.weapons.keys[tpl_args.class_id] ~= nil
 
return cfg.class_groups.weapons.keys[tpl_args.class_id] ~= nil
 
end,
 
end,
func = function(tpl_args, frame)
+
func = function (tpl_args, frame)
 
local v = i18n.item_class_map[tpl_args.class_id]
 
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'})
 
return m_util.html.format_value(tpl_args, frame, {min=v, max=v}, {color = 'default'})
Строка 859: Строка 500:
 
-- Hideout item type
 
-- Hideout item type
 
{
 
{
show = function(tpl_args, frame)
+
show = function (tpl_args, frame)
 
return tpl_args.class_id == 'HideoutDoodad'
 
return tpl_args.class_id == 'HideoutDoodad'
 
end,
 
end,
func = function(tpl_args, frame)
+
func = function (tpl_args, frame)
 
return i18n.item_class_map[tpl_args.class_id]
 
return i18n.item_class_map[tpl_args.class_id]
 
end,
 
end,
 
},
 
},
 
{
 
{
show = h.factory.args_present('gem_tags'),
+
show = h.factory.maybe_show_infobox_line{
func = function(tpl_args, frame)
+
keys = {'gem_tags'},
  +
conditions = {processed = true},
  +
},
  +
func = function (tpl_args, frame)
 
local out = {}
 
local out = {}
 
for i, tag in ipairs(tpl_args.gem_tags) do
 
for i, tag in ipairs(tpl_args.gem_tags) do
 
out[#out+1] = string.format(i18n.gem_tag_category, tag, tag)
 
out[#out+1] = string.format(i18n.gem_tag_category, tag, tag)
 
end
 
end
 
 
return table.concat(out, ', ')
 
return table.concat(out, ', ')
 
end,
 
end,
 
},
 
},
 
{
 
{
show = h.factory.args_present('support_gem_letter_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'support_gem_letter_html'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 889: Строка 535:
 
},
 
},
 
{
 
{
show = h.factory.args_present('radius'),
+
show = function (tpl_args, frame)
  +
return tpl_args.skill_levels and cfg.class_groups.gems.keys[tpl_args.class_id]
func = core.factory.infobox_line{
 
parts = {
+
end,
{
+
func = function (tpl_args, frame)
key = 'radius',
+
local value = {
func = h.factory.descriptor_value{key='radius_description'},
+
base = 1,
},
+
min = 1,
{
+
max = tpl_args.max_level,
key = 'radius_secondary',
+
}
func = h.factory.descriptor_value{key='radius_secondary_description'},
+
local options = {
},
+
color = 'value',
{
+
}
key = 'radius_tertiary',
+
return string.format(
func = h.factory.descriptor_value{key='radius_tertiary_description'},
+
i18n.tooltips.level,
},
+
m_util.html.format_value(tpl_args, frame, value, options)
},
+
)
sep = ' / ',
+
end,
fmt = i18n.tooltips.radius,
 
},
 
 
},
 
},
-- TODO: gem level here. Maybe put max level here?
 
 
{
 
{
  +
show = function (tpl_args, frame)
-- TODO: Rework for 3.14.0 skill cost and reservation changes
 
show = true,
+
return tpl_args.skill_costs
func = core.factory.infobox_line{
+
end,
type = 'gem',
+
func = function (tpl_args, frame)
parts = {
+
local parts = {}
{
+
if not tpl_args.skill_costs.has_spending_cost then
key = 'mana_cost',
+
-- Try falling back to deprecated parameters
hide_default = 100,
+
if not tpl_args.has_reservation_mana_cost then
fmt = function (tpl_args, frame)
+
parts[1] = {
if tpl_args.has_percentage_mana_cost then
+
key = 'mana_cost',
return '%i%%'
+
fmt = '%i',
else
+
color = false,
return '%i'
+
inline = string.format('%%s %s', m_game.constants.skill.cost_types.mana.long_upper),
end
+
inline_color = 'value',
end,
+
}
inline = function (tpl_args, frame)
+
end
if tpl_args.has_reservation_mana_cost then
+
else
return i18n.tooltips.mana_reserved
+
for i=1, #tpl_args.skill_costs do
else
+
if not tpl_args.skill_costs[i].is_reservation then -- Only get spending costs
return i18n.tooltips.mana_cost
+
local cost_type = tpl_args.skill_costs[i].type
end
+
parts[#parts+1] = {
end,
+
key = {'costs', i, 'amount'},
},
+
fmt = '%i',
{
+
color = false,
key = 'life_cost',
+
inline = string.format(
hide_default = 100,
+
'%%s%s %s',
fmt = '%i',
+
string.find(cost_type, 'percent', 1, true) and '%%' or '',
inline = function (tpl_args, frame)
+
m_game.constants.skill.cost_types[cost_type].long_upper
return i18n.tooltips.life_cost
+
),
end,
+
inline_color = 'value',
},
+
}
},
+
end
},
+
end
  +
end
  +
return core.factory.infobox_line{
  +
type = 'gem',
  +
parts = parts,
  +
sep = ', ',
  +
fmt = i18n.tooltips.cost,
  +
}(tpl_args, frame)
  +
end,
  +
},
  +
{
  +
show = function (tpl_args, frame)
  +
return tpl_args.skill_costs
  +
end,
  +
func = function (tpl_args, frame)
  +
if not tpl_args.skill_costs then
  +
return
  +
end
  +
local parts = {}
  +
if not tpl_args.skill_costs.has_reservation_cost then
  +
-- Try falling back to deprecated parameters
  +
if tpl_args.has_reservation_mana_cost then
  +
parts[1] = {
  +
key = 'mana_cost',
  +
fmt = '%i',
  +
color = false,
  +
inline = string.format(
  +
'%%s%s %s',
  +
tpl_args.has_percentage_mana_cost and '%%' or '',
  +
m_game.constants.skill.cost_types.mana.long_upper
  +
),
  +
inline_color = 'value',
  +
}
  +
end
  +
else
  +
for i=1, #tpl_args.skill_costs do
  +
if tpl_args.skill_costs[i].is_reservation then -- Only get reservation costs
  +
local cost_type = tpl_args.skill_costs[i].type
  +
parts[#parts+1] = {
  +
key = {'costs', i, 'amount'},
  +
fmt = '%i',
  +
color = false,
  +
inline = string.format(
  +
'%%s%s %s',
  +
string.find(cost_type, 'percent', 1, true) and '%%' or '',
  +
m_game.constants.skill.cost_types[cost_type].long_upper
  +
),
  +
inline_color = 'value',
  +
}
  +
end
  +
end
  +
end
  +
return core.factory.infobox_line{
  +
type = 'gem',
  +
parts = parts,
  +
sep = ', ',
  +
fmt = i18n.tooltips.reservation,
  +
}(tpl_args, frame)
  +
end,
 
},
 
},
 
{
 
{
Строка 953: Строка 654:
 
key = 'mana_multiplier',
 
key = 'mana_multiplier',
 
hide_default = 100,
 
hide_default = 100,
fmt = '%i%%',
+
fmt = '%i',
  +
color = false,
  +
inline = '%s%%',
  +
inline_color = 'value',
 
},
 
},
 
},
 
},
Строка 1032: Строка 736:
 
},
 
},
 
{
 
{
show = h.factory.args_present('cast_time'),
+
show = function (tpl_args, frame)
  +
return tpl_args.cast_time and tpl_args.gem_tags and (m_util.table.contains(tpl_args.gem_tags, m_game.constants.item.gem_tags.spell.tag) or m_util.table.contains(tpl_args.gem_tags, m_game.constants.item.gem_tags.warcry.tag))
func = core.factory.infobox_line{
 
parts = {
+
end,
{
+
func = function (tpl_args, frame)
key = 'cast_time',
+
return core.factory.infobox_line{
fmt = function (tpl_args, frame, value)
+
parts = {
if value.min == 0 then
+
{
return i18n.tooltips.instant_cast_time
+
key = 'cast_time',
end
+
fmt = function (tpl_args, frame, value)
return '%.2f ' .. m_game.units.seconds.short_lower
+
if value.min == 0 then
end,
+
return i18n.tooltips.instant_cast_time
  +
end
  +
return '%.2f ' .. m_game.units.seconds.short_lower
  +
end,
  +
},
 
},
 
},
  +
fmt = m_util.table.contains(tpl_args.gem_tags, m_game.constants.item.gem_tags.spell.tag) and i18n.tooltips.cast_time or i18n.tooltips.use_time,
},
 
fmt = i18n.tooltips.cast_time,
+
}(tpl_args, frame)
},
+
end,
 
},
 
},
 
{
 
{
Строка 1070: Строка 778:
 
key = 'attack_speed_multiplier',
 
key = 'attack_speed_multiplier',
 
hide_default = 100,
 
hide_default = 100,
fmt = '%i%%',
+
fmt = '%i',
inline = '%s ' .. i18n.tooltips.of_base_stat,
+
color = false,
  +
inline = '%s%% ' .. i18n.tooltips.of_base_stat,
 
inline_color = 'value',
 
inline_color = 'value',
 
},
 
},
Строка 1086: Строка 795:
 
key = 'damage_multiplier',
 
key = 'damage_multiplier',
 
hide_default = 100,
 
hide_default = 100,
fmt = '%i%%',
+
fmt = '%i',
inline = '%s ' .. i18n.tooltips.of_base_stat,
+
color = false,
  +
inline = '%s%% ' .. i18n.tooltips.of_base_stat,
 
inline_color = 'value',
 
inline_color = 'value',
 
},
 
},
Строка 1102: Строка 812:
 
key = 'damage_effectiveness',
 
key = 'damage_effectiveness',
 
hide_default = 100,
 
hide_default = 100,
fmt = '%i%%',
+
fmt = '%i',
  +
color = false,
  +
inline = '%s%%',
  +
inline_color = 'value',
 
},
 
},
 
},
 
},
Строка 1109: Строка 822:
 
},
 
},
 
{
 
{
show = h.factory.args_present('projectile_speed'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'projectile_speed'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1117: Строка 832:
 
},
 
},
 
fmt = i18n.tooltips.projectile_speed,
 
fmt = i18n.tooltips.projectile_speed,
  +
},
  +
},
  +
{
  +
show = h.factory.maybe_show_infobox_line{
  +
keys = {'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,
 
},
 
},
 
},
 
},
 
-- Quality is before item stats, but after gem stuff and item class
 
-- Quality is before item stats, but after gem stuff and item class
 
{
 
{
show = h.factory.args_present('quality'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'quality'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1136: Строка 877:
 
-- Weapon only
 
-- Weapon only
 
{
 
{
show = h.factory.args_present('physical_damage_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'physical_damage_html'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1149: Строка 893:
 
{
 
{
 
show = true, -- Elemental Damage
 
show = true, -- Elemental Damage
func = function(tpl_args, frame)
+
func = function (tpl_args, frame)
 
local keys = {'fire_damage_html', 'cold_damage_html', 'lightning_damage_html'}
 
local keys = {'fire_damage_html', 'cold_damage_html', 'lightning_damage_html'}
 
local elements = {}
 
local elements = {}
Строка 1165: Строка 909:
 
},
 
},
 
{
 
{
show = h.factory.args_present('chaos_damage_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'chaos_damage_html'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1178: Строка 925:
 
},
 
},
 
{
 
{
show = h.factory.args_present('critical_strike_chance_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'critical_strike_chance_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1190: Строка 939:
 
},
 
},
 
{
 
{
show = h.factory.args_present('attack_speed_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'attack_speed_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1202: Строка 953:
 
},
 
},
 
{
 
{
show = h.factory.args_present('weapon_range_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'weapon_range_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1215: Строка 968:
 
-- Map only
 
-- Map only
 
{
 
{
show = h.factory.args_present('map_area_level'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'map_area_level'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1227: Строка 983:
 
},
 
},
 
{
 
{
show = h.factory.args_present('map_tier'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'map_tier'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1239: Строка 998:
 
},
 
},
 
{
 
{
show = function(tpl_args, frame)
+
show = function (tpl_args, frame)
 
return tpl_args.map_guild_character ~= nil and tpl_args.rarity_id == 'normal'
 
return tpl_args.map_guild_character ~= nil and tpl_args.rarity_id == 'normal'
 
end,
 
end,
Строка 1253: Строка 1012:
 
},
 
},
 
{
 
{
show = function(tpl_args, frame)
+
show = function (tpl_args, frame)
 
return tpl_args.unique_map_guild_character ~= nil and tpl_args.rarity_id == 'unique'
 
return tpl_args.unique_map_guild_character ~= nil and tpl_args.rarity_id == 'unique'
 
end,
 
end,
Строка 1325: Строка 1084:
 
},
 
},
 
{
 
{
show = h.factory.args_present('jewel_radius_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'jewel_radius_html'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1339: Строка 1101:
 
-- Flask only
 
-- Flask only
 
{
 
{
show = h.factory.args_present('flask_mana_html', 'flask_duration_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'flask_mana_html', 'flask_duration_html'},
  +
},
 
--func = core.factory.display_flask('flask_mana'),
 
--func = core.factory.display_flask('flask_mana'),
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
Строка 1356: Строка 1120:
 
},
 
},
 
{
 
{
show = h.factory.args_present('flask_life_html', 'flask_duration_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'flask_life_html', 'flask_duration_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1373: Строка 1139:
 
{
 
{
 
-- don't display for mana/life flasks
 
-- don't display for mana/life flasks
show = function(tpl_args, frame)
+
show = function (tpl_args, frame)
 
for _, k in ipairs({'flask_life_html', 'flask_mana_html'}) do
 
for _, k in ipairs({'flask_life_html', 'flask_mana_html'}) do
 
if tpl_args[k] ~= nil then
 
if tpl_args[k] ~= nil then
Строка 1392: Строка 1158:
 
},
 
},
 
{
 
{
show = h.factory.args_present('charges_per_use_html', 'charges_max_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'charges_per_use_html', 'charges_max_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1408: Строка 1176:
 
},
 
},
 
{
 
{
show = h.factory.args_present('buff_stat_text'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'buff_stat_text'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1418: Строка 1189:
 
},
 
},
 
},
 
},
-- armor
+
-- Armor only
 
{
 
{
show = h.factory.args_present('block_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'block_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1434: Строка 1207:
 
},
 
},
 
{
 
{
show = h.factory.args_present('armour_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'armour_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1448: Строка 1223:
 
},
 
},
 
{
 
{
show = h.factory.args_present('evasion_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'evasion_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1462: Строка 1239:
 
},
 
},
 
{
 
{
show = h.factory.args_present('energy_shield_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'energy_shield_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1476: Строка 1255:
 
},
 
},
 
{
 
{
show = h.factory.args_present('movement_speed'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'ward_html'},
  +
},
  +
func = core.factory.infobox_line{
  +
parts = {
  +
{
  +
key = 'ward_html',
  +
fmt = '%s',
  +
hide_default = 0,
  +
hide_default_key = 'ward',
  +
},
  +
},
  +
fmt = i18n.tooltips.ward,
  +
},
  +
},
  +
{
  +
show = h.factory.maybe_show_infobox_line{
  +
keys = {'movement_speed'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1490: Строка 1288:
 
-- Amulet only
 
-- Amulet only
 
{
 
{
show = h.factory.args_present('talisman_tier'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'talisman_tier'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1503: Строка 1304:
 
-- Misc
 
-- Misc
 
{
 
{
show = h.factory.args_present('stack_size'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'stack_size'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1517: Строка 1321:
 
-- Essence stuff
 
-- Essence stuff
 
{
 
{
show = h.factory.args_present('essence_level'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'essence_level'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1530: Строка 1337:
 
-- Blight items
 
-- Blight items
 
{
 
{
show = h.factory.args_present('blight_item_tier'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'blight_item_tier'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1541: Строка 1351:
 
},
 
},
 
},
 
},
-- Harvest seeds (upper section)
+
-- Harvest seeds (upper section)
 
{
 
{
show = h.factory.args_present('seed_tier'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'seed_tier'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1555: Строка 1368:
 
},
 
},
 
{
 
{
show = h.factory.args_present('seed_tier'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'seed_tier'},
  +
conditions = {processed = true},
  +
},
 
func = function (tpl_args, value)
 
func = function (tpl_args, value)
 
return i18n.tooltips.seed_monster
 
return i18n.tooltips.seed_monster
Строка 1561: Строка 1377:
 
},
 
},
 
{
 
{
show = h.factory.args_present('seed_type_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'seed_type_html'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1573: Строка 1392:
 
},
 
},
 
{
 
{
show = h.factory.args_present('seed_growth_cycles'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'seed_growth_cycles'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1584: Строка 1406:
 
},
 
},
 
},
 
},
-- Harvest plant boosters
+
-- Heist
-- TODO: Remaining enhancements
 
 
{
 
{
show = h.factory.args_present('plant_booster_radius'),
+
show = h.factory.maybe_show_infobox_line{
func = core.factory.infobox_line{
+
keys = {'heist_required_npcs'},
parts = {
 
{
 
key = 'plant_booster_radius',
 
fmt = '%s',
 
},
 
},
 
fmt = i18n.tooltips.radius,
 
 
},
 
},
},
 
{
 
show = h.factory.args_present('heist_required_npcs'),
 
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1613: Строка 1424:
 
-- Requirements
 
-- 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 %s]]', data.full, data.short_upper, tpl_args.master_level_requirement))
 
end
 
},
 
 
-- Instead of item level, show drop level if any
 
-- Instead of item level, show drop level if any
 
{
 
{
 
show = true, -- Requires...
 
show = true, -- Requires...
func = function(tpl_args, frame)
+
func = function (tpl_args, frame)
 
local parts = {
 
local parts = {
 
{
 
{
Строка 1663: Строка 1458:
 
},
 
},
 
{
 
{
show = h.factory.args_present('heist_required_job', 'heist_required_job_level'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'heist_required_job', 'heist_required_job_level'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1683: Строка 1480:
 
class = 'tc -gemdesc',
 
class = 'tc -gemdesc',
 
{
 
{
show = h.factory.args_present('gem_description'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'gem_description'},
  +
},
 
func = h.factory.display_raw_value('gem_description'),
 
func = h.factory.display_raw_value('gem_description'),
 
},
 
},
Строка 1691: Строка 1490:
 
class = 'tc -mod',
 
class = 'tc -mod',
 
{
 
{
show = h.factory.args_present('skill_quality'),
+
show = h.factory.maybe_show_infobox_line{
func = function(tpl_args, frame)
+
keys = {'skill_quality'},
  +
},
  +
func = function (tpl_args, frame)
 
local span = mw.html.create('span')
 
local span = mw.html.create('span')
 
span
 
span
Строка 1737: Строка 1538:
 
class = 'tc -mod',
 
class = 'tc -mod',
 
{
 
{
show = function(tpl_args, frame)
+
show = function (tpl_args, frame)
 
return cfg.class_groups.gems.keys[tpl_args.class_id] and tpl_args.stat_text
 
return cfg.class_groups.gems.keys[tpl_args.class_id] and tpl_args.stat_text
 
end,
 
end,
func = function(tpl_args, frame)
+
func = function (tpl_args, frame)
lines = {}
+
local lines = {}
 
lines[#lines+1] = tpl_args.stat_text
 
lines[#lines+1] = tpl_args.stat_text
 
for _, tag in ipairs(tpl_args.gem_tags) do
 
for _, tag in ipairs(tpl_args.gem_tags) do
Строка 1756: Строка 1557:
 
{
 
{
 
class = 'tc -mod',
 
class = 'tc -mod',
func = function(tpl_args, frame, container_type)
+
func = function (tpl_args, frame, container_type)
 
if tpl_args.implicit_stat_text then
 
if tpl_args.implicit_stat_text then
 
return {h.strip_random_stats(tpl_args, frame, tpl_args.implicit_stat_text, container_type)}
 
return {h.strip_random_stats(tpl_args, frame, tpl_args.implicit_stat_text, container_type)}
Строка 1767: Строка 1568:
 
{
 
{
 
class = 'tc -mod',
 
class = 'tc -mod',
func = function(tpl_args, frame, container_type)
+
func = function (tpl_args, frame, container_type)
 
if tpl_args.explicit_stat_text then
 
if tpl_args.explicit_stat_text then
 
return {h.strip_random_stats(tpl_args, frame, tpl_args.explicit_stat_text, container_type)}
 
return {h.strip_random_stats(tpl_args, frame, tpl_args.explicit_stat_text, container_type)}
Строка 1778: Строка 1579:
 
--[[{
 
--[[{
 
{
 
{
show = h.factory.args_present('experience'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'experience'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1793: Строка 1596:
 
class = 'tc -mod',
 
class = 'tc -mod',
 
{
 
{
show = h.factory.args_present('seed_consumed_wild_lifeforce_percentage'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'seed_consumed_wild_lifeforce_percentage'},
  +
conditions = {processed = true},
  +
},
 
func = function (tpl_args, frame)
 
func = function (tpl_args, frame)
 
if tpl_args.seed_consumed_wild_lifeforce_percentage > 0 then
 
if tpl_args.seed_consumed_wild_lifeforce_percentage > 0 then
Строка 1801: Строка 1607:
 
},
 
},
 
{
 
{
show = h.factory.args_present('seed_consumed_vivid_lifeforce_percentage'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'seed_consumed_vivid_lifeforce_percentage'},
  +
conditions = {processed = true},
  +
},
 
func = function (tpl_args, frame)
 
func = function (tpl_args, frame)
 
if tpl_args.seed_consumed_vivid_lifeforce_percentage > 0 then
 
if tpl_args.seed_consumed_vivid_lifeforce_percentage > 0 then
Строка 1809: Строка 1618:
 
},
 
},
 
{
 
{
show = h.factory.args_present('seed_consumed_primal_lifeforce_percentage'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'seed_consumed_primal_lifeforce_percentage'},
  +
conditions = {processed = true},
  +
},
 
func = function (tpl_args, frame)
 
func = function (tpl_args, frame)
 
if tpl_args.seed_consumed_primal_lifeforce_percentage > 0 then
 
if tpl_args.seed_consumed_primal_lifeforce_percentage > 0 then
Строка 1817: Строка 1629:
 
},
 
},
 
{
 
{
show = h.factory.args_present('seed_required_nearby_seed_tier', 'seed_type_html', 'seed_required_nearby_seed_amount'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'seed_required_nearby_seed_tier', 'seed_type_html', 'seed_required_nearby_seed_amount'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1841: Строка 1656:
 
class = 'tc -crafted',
 
class = 'tc -crafted',
 
{
 
{
show = h.factory.args_present('seed_effect'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'seed_effect'},
  +
conditions = {processed = true},
  +
},
 
func = h.factory.display_raw_value('seed_effect'),
 
func = h.factory.display_raw_value('seed_effect'),
 
},
 
},
Строка 1849: Строка 1667:
 
class = 'tc -mod',
 
class = 'tc -mod',
 
{
 
{
show = h.factory.args_present('description'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'description'},
  +
conditions = {processed = true},
  +
},
 
func = h.factory.display_raw_value('description'),
 
func = h.factory.display_raw_value('description'),
 
},
 
},
 
--[[{
 
--[[{
show = h.factory.args_present('plant_booster_additional_crafting_options'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'plant_booster_additional_crafting_options'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1865: Строка 1689:
 
},
 
},
 
{
 
{
show = h.factory.args_present('plant_booster_extra_chances'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'plant_booster_extra_chances'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1877: Строка 1704:
 
},
 
},
 
{
 
{
show = h.factory.args_present('plant_booster_lifeforce'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'plant_booster_lifeforce'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 1892: Строка 1722:
 
class = 'tc -crafted',
 
class = 'tc -crafted',
 
{
 
{
show = h.factory.args_present('incubator_effect'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'incubator_effect'},
  +
conditions = {processed = true},
  +
},
 
func = h.factory.display_raw_value('incubator_effect'),
 
func = h.factory.display_raw_value('incubator_effect'),
 
},
 
},
Строка 1900: Строка 1733:
 
class = 'tc -mod',
 
class = 'tc -mod',
 
{
 
{
show = h.factory.args_present('variation_count'),
+
show = h.factory.maybe_show_infobox_line{
func = function(tpl_args, frame)
+
keys = {'variation_count'},
  +
conditions = {processed = true},
  +
},
  +
func = function (tpl_args, frame)
 
local txt
 
local txt
 
if tpl_args.variation_count == 1 then
 
if tpl_args.variation_count == 1 then
Строка 1916: Строка 1752:
 
class = 'tc -flavour',
 
class = 'tc -flavour',
 
{
 
{
show = h.factory.args_present('flavour_text'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'flavour_text'},
  +
conditions = {processed = true},
  +
},
 
func = h.factory.display_raw_value('flavour_text'),
 
func = h.factory.display_raw_value('flavour_text'),
 
},
 
},
Строка 1924: Строка 1763:
 
class = 'tc -value',
 
class = 'tc -value',
 
{
 
{
show = h.factory.args_present('prediction_text'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'prediction_text'},
  +
conditions = {processed = true},
  +
},
 
func = h.factory.display_raw_value('prediction_text'),
 
func = h.factory.display_raw_value('prediction_text'),
 
},
 
},
Строка 1932: Строка 1774:
 
class = 'tc -canttradeormodify',
 
class = 'tc -canttradeormodify',
 
{
 
{
show = h.factory.args_present('cannot_be_traded_or_modified'),
+
show = h.factory.maybe_show_infobox_line{
func = function(tpl_args, frame)
+
keys = {'cannot_be_traded_or_modified'},
  +
conditions = {processed = true},
  +
},
  +
func = function (tpl_args, frame)
 
if tpl_args.cannot_be_traded_or_modified == true then
 
if tpl_args.cannot_be_traded_or_modified == true then
 
return i18n.tooltips.cannot_be_traded_or_modified
 
return i18n.tooltips.cannot_be_traded_or_modified
Строка 1944: Строка 1789:
 
class = 'tc -help',
 
class = 'tc -help',
 
{
 
{
show = h.factory.args_present('help_text'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'help_text'},
  +
conditions = {processed = true},
  +
},
 
func = h.factory.display_raw_value('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,
 
},
 
 
},
 
},
 
},
 
},
Строка 1989: Строка 1806:
 
class = '',
 
class = '',
 
{
 
{
show = h.factory.args_present('atlas_connections'),
+
show = h.factory.maybe_show_infobox_line{
func = function(tpl_args, frame)
+
keys = {'atlas_connections'},
  +
conditions = {processed = true},
  +
},
  +
func = function (tpl_args, frame)
 
local fields = {
 
local fields = {
 
[false] = {
 
[false] = {
Строка 2097: Строка 1917:
 
},
 
},
 
},
 
},
-- Drop info
+
-- Acquisition
 
{
 
{
heading = i18n.tooltips.drop_restrictions,
+
heading = i18n.tooltips.acquisition,
class = '',
 
 
{
 
{
show = h.factory.args_present('drop_enabled'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'drop_level'},
  +
conditions = {processed = true},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
Строка 2117: Строка 1939:
 
},
 
},
 
sep = ' / ',
 
sep = ' / ',
fmt = i18n.tooltips.level,
+
fmt = i18n.tooltips.area_level,
 
},
 
},
 
},
 
},
 
{
 
{
show = h.factory.args_present('drop_leagues'),
+
show = function (tpl_args, frame)
func = function(tpl_args, frame)
+
if tpl_args.drop_enabled == false then
  +
return true
  +
end
  +
return false
  +
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,
  +
},
  +
{
  +
show = function (tpl_args, frame)
  +
if tpl_args.is_drop_restricted == true and tpl_args.drop_enabled ~= false then
  +
return true
  +
end
  +
return false
  +
end,
  +
func = function (tpl_args, frame)
  +
local span = mw.html.create('span')
  +
span
  +
:attr('class', 'infobox-restricted-drop')
  +
:wikitext(i18n.tooltips.drop_restricted)
  +
:done()
  +
return tostring(span)
  +
end,
  +
},
  +
{
  +
show = h.factory.maybe_show_infobox_line{
  +
keys = {'drop_leagues'},
  +
conditions = {processed = true},
  +
},
  +
func = function (tpl_args, frame)
 
return string.format(i18n.tooltips.league_restriction, m_util.html.poe_color('value', table.concat(tpl_args.drop_leagues, ', ')))
 
return string.format(i18n.tooltips.league_restriction, m_util.html.poe_color('value', table.concat(tpl_args.drop_leagues, ', ')))
 
end
 
end
 
},
 
},
 
{
 
{
show = h.factory.args_present('drop_areas_html'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'drop_areas_html'},
  +
conditions = {processed = true},
  +
},
 
func = h.factory.display_raw_value('drop_areas_html'),
 
func = h.factory.display_raw_value('drop_areas_html'),
 
},
 
},
 
{
 
{
show = h.factory.args_present('drop_text'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'drop_text'},
  +
conditions = {processed = true},
  +
},
 
func = h.factory.display_raw_value('drop_text'),
 
func = h.factory.display_raw_value('drop_text'),
 
},
 
},
 
{
 
{
show = function(tpl_args, frame)
+
show = h.factory.maybe_show_infobox_line{
if tpl_args.drop_enabled == true then
+
keys = {'master', 'master_level_requirement'},
return false
+
conditions = {processed = true},
  +
},
  +
func = function (tpl_args, frame)
  +
local master
  +
for _, v in ipairs(m_game.constants.masters) do
  +
if tpl_args.master == v.full then
  +
master = v
  +
break
  +
end
 
end
 
end
  +
local text = string.format('[[%s|%s]] ', master.full, master.short_upper) .. string.format(i18n.tooltips.master_level, tpl_args.master_level_requirement)
return true
 
  +
return string.format(i18n.tooltips.master, m_util.html.poe_color('value', text))
end,
 
func = function(tpl_args, frame)
+
end
  +
},
local span = mw.html.create('span')
 
span
+
{
:attr('class', 'infobox-disabled-drop')
+
show = h.factory.maybe_show_infobox_line{
:wikitext(i18n.tooltips.drop_disabled)
+
keys = {'master', 'master_favour_cost'},
:done()
+
conditions = {processed = true},
return tostring(span)
+
},
end,
+
func = core.factory.infobox_line{
  +
parts = {
  +
{
  +
key = 'master_favour_cost',
  +
color = 'currency',
  +
hide_default = 0,
  +
},
  +
},
  +
fmt = i18n.tooltips.favour_cost,
  +
},
  +
},
  +
{
  +
show = h.factory.maybe_show_infobox_line{
  +
keys = {'seal_cost'},
  +
conditions = {processed = true},
  +
},
  +
func = core.factory.infobox_line{
  +
parts = {
  +
{
  +
key = 'seal_cost',
  +
fmt = function (tpl_args, frame, value)
  +
return string.format('%s %s', i18n.fmt.item_count, h.item_link{metadata_id='Metadata/Items/Currency/CurrencySilverCoin', html=''})
  +
end,
  +
color = 'currency',
  +
},
  +
},
  +
fmt = i18n.tooltips.seal_cost,
  +
},
 
},
 
},
 
},
 
},
 
{
 
{
  +
-- Vendor prices
 
heading = i18n.tooltips.purchase_costs,
 
heading = i18n.tooltips.purchase_costs,
 
{
 
{
show = function(tpl_args, frame)
+
show = function (tpl_args, frame)
 
for rarity, data in pairs(tpl_args.purchase_costs) do
 
for rarity, data in pairs(tpl_args.purchase_costs) do
 
if #data > 0 then
 
if #data > 0 then
Строка 2162: Строка 2061:
 
return false
 
return false
 
end,
 
end,
func = function(tpl_args, frame)
+
func = function (tpl_args, frame)
 
local tbl = mw.html.create('table')
 
local tbl = mw.html.create('table')
 
tbl
 
tbl
Строка 2187: Строка 2086:
 
},
 
},
 
{
 
{
  +
-- Vendor offer
 
heading = i18n.tooltips.sell_price,
 
heading = i18n.tooltips.sell_price,
 
{
 
{
show = h.factory.args_present('sell_price_order'),
+
show = h.factory.maybe_show_infobox_line{
func = function(tpl_args, frame)
+
keys = {'sell_price_order'},
  +
},
  +
func = function (tpl_args, frame)
 
local out = {}
 
local out = {}
for _, item_name in ipairs(tpl_args.sell_price_order) do
+
if #tpl_args.sell_price_order > 0 then
out[#out+1] = string.format('%dx [[%s]]', tpl_args.sell_prices[item_name], item_name)
+
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
 
end
 
end
 
 
return table.concat(out, '<br />')
 
return table.concat(out, '<br />')
 
end,
 
end,
Строка 2203: Строка 2106:
 
{
 
{
 
heading = i18n.tooltips.damage_per_second,
 
heading = i18n.tooltips.damage_per_second,
-- Autoinsert here from dps map
 
},
 
{
 
heading = i18n.tooltips.misc,
 
 
{
 
{
show = h.factory.args_present('class'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'physical_dps_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
 
{
 
{
key = 'class',
+
key = 'physical_dps_html',
 
fmt = '%s',
 
fmt = '%s',
  +
color = false, -- the html already contains the colour
 
},
 
},
 
},
 
},
fmt = i18n.tooltips.item_class,
+
fmt = i18n.tooltips.physical_dps,
 
},
 
},
 
},
 
},
 
{
 
{
show = h.factory.args_present('metadata_id'),
+
show = h.factory.maybe_show_infobox_line{
  +
keys = {'fire_dps_html'},
  +
},
 
func = core.factory.infobox_line{
 
func = core.factory.infobox_line{
 
parts = {
 
parts = {
 
{
 
{
key = 'metadata_id',
+
key = 'fire_dps_html',
 
fmt = '%s',
 
fmt = '%s',
truncate = true,
+
color = false, -- the html already contains the colour
 
},
 
},
 
},
 
},
fmt = i18n.tooltips.metadata_id,
+
fmt = i18n.tooltips.fire_dps,
  +
},
  +
},
  +
{
  +
show = h.factory.maybe_show_infobox_line{
  +
keys = {'cold_dps_html'},
  +
},
  +
func = core.factory.infobox_line{
  +
parts = {
  +
{
  +
key = 'cold_dps_html',
  +
fmt = '%s',
  +
color = false, -- the html already contains the colour
  +
},
  +
},
  +
fmt = i18n.tooltips.cold_dps,
  +
},
  +
},
  +
{
  +
show = h.factory.maybe_show_infobox_line{
  +
keys = {'lightning_dps_html'},
  +
},
  +
func = core.factory.infobox_line{
  +
parts = {
  +
{
  +
key = 'lightning_dps_html',
  +
fmt = '%s',
  +
color = false, -- the html already contains the colour
  +
},
  +
},
  +
fmt = i18n.tooltips.lightning_dps,
  +
},
  +
},
  +
{
  +
show = h.factory.maybe_show_infobox_line{
  +
keys = {'chaos_dps_html'},
  +
},
  +
func = core.factory.infobox_line{
  +
parts = {
  +
{
  +
key = 'chaos_dps_html',
  +
fmt = '%s',
  +
color = false, -- the html already contains the colour
  +
},
  +
},
  +
fmt = i18n.tooltips.chaos_dps,
  +
},
  +
},
  +
{
  +
show = h.factory.maybe_show_infobox_line{
  +
keys = {'elemental_dps_html'},
  +
},
  +
func = core.factory.infobox_line{
  +
parts = {
  +
{
  +
key = 'elemental_dps_html',
  +
fmt = '%s',
  +
color = false, -- the html already contains the colour
  +
},
  +
},
  +
fmt = i18n.tooltips.elemental_dps,
  +
},
  +
},
  +
{
  +
show = h.factory.maybe_show_infobox_line{
  +
keys = {'poison_dps_html'},
  +
},
  +
func = core.factory.infobox_line{
  +
parts = {
  +
{
  +
key = 'poison_dps_html',
  +
fmt = '%s',
  +
color = false, -- the html already contains the colour
  +
},
  +
},
  +
fmt = i18n.tooltips.poison_dps,
  +
},
  +
},
  +
{
  +
show = h.factory.maybe_show_infobox_line{
  +
keys = {'dps_html'},
  +
},
  +
func = core.factory.infobox_line{
  +
parts = {
  +
{
  +
key = 'dps_html',
  +
fmt = '%s',
  +
color = false, -- the html already contains the colour
  +
},
  +
},
  +
fmt = i18n.tooltips.dps,
 
},
 
},
 
},
 
},
 
},
 
},
  +
{
}
 
  +
heading = i18n.tooltips.metadata,
 
  +
{
for i, data in ipairs(core.dps_map) do
 
  +
show = h.factory.maybe_show_infobox_line{
table.insert(c.extra_display_groups[4], {
 
show = h.factory.args_present(data.name .. '_html'),
+
keys = {'class'},
  +
conditions = {processed = true},
func = core.factory.infobox_line{
 
parts = {
+
},
{
+
func = core.factory.infobox_line{
key = data.name .. '_html',
+
parts = {
inline = data.label_infobox .. ': %s',
+
{
fmt = '%s',
+
key = 'class',
-- the html already contains the colour
+
fmt = '%s',
color = false,
+
},
 
},
 
},
  +
fmt = i18n.tooltips.item_class,
 
},
 
},
 
},
 
},
})
+
{
  +
show = h.factory.maybe_show_infobox_line{
if i == 5 then
 
  +
keys = {'metadata_id'},
table.insert(c.extra_display_groups[4], {
 
show = function (tpl_args, frame)
+
conditions = {processed = true},
  +
},
return tpl_args.elemental_dps_html ~= nil or tpl_args.poison_dps_html ~= nil
 
end,
 
 
func = function (tpl_args, frame)
 
func = function (tpl_args, frame)
return ''
+
return core.factory.infobox_line{
  +
parts = {
  +
{
  +
key = 'metadata_id',
  +
fmt = '%s',
  +
inline = m_util.html.abbr('%s', tpl_args.metadata_id),
  +
},
  +
},
  +
fmt = tostring(
  +
mw.html.create('span')
  +
:addClass('u-truncate-line')
  +
:wikitext(i18n.tooltips.metadata_id)
  +
),
  +
}(tpl_args, frame)
 
end,
 
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
 
   
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
  +
-- Subroutines
-- Exported functions
 
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
   
local p = {}
+
local s = {}
   
  +
-- Subroutines for p.item. These exist to declutter the main function. Each one of these is only called once.
--
 
  +
-- Template:Item
 
  +
function s.get_item_config(tpl_args, frame)
--
 
  +
-- Returns primary item configuration, based on item class
function p.item(frame)
 
  +
local t = os.clock()
 
  +
local config = {
  +
tables = {}, -- Table names that data may be stored to
  +
args = {}, -- Args to process early
  +
late_args = {}, -- Args to process late
  +
defaults = {}, -- Defaults based on item class
  +
}
  +
for _, v in ipairs(cfg.tables) do
  +
table.insert(config.tables, v)
  +
end
  +
for _, v in ipairs(cfg.default_args) do
  +
table.insert(config.args, v)
  +
end
  +
for _, v in ipairs(cfg.late_args) do
  +
table.insert(config.late_args, v)
  +
end
  +
  +
-- Process class_id
  +
h.process_arguments(tpl_args, frame, {'class_id'})
  +
  +
local extend_keys = {'tables', 'args', 'late_args'} -- We will do defaults separately to preserve order of args
  +
for _, row in pairs(cfg.class_groups) do
  +
if row.keys[tpl_args.class_id] then
  +
for _, k in ipairs(extend_keys) do
  +
if row[k] then
  +
for _, v in ipairs(row[k]) do
  +
table.insert(config[k], v)
  +
end
  +
end
  +
end
  +
if row['defaults'] then
  +
for k, v in pairs(row['defaults']) do
  +
config['defaults'][k] = v
  +
end
  +
end
  +
break
  +
end
  +
end
  +
local class_specifics = cfg.class_specifics[tpl_args.class_id]
  +
if class_specifics then
  +
for _, k in ipairs(extend_keys) do
  +
if class_specifics[k] then
  +
for _, v in ipairs(class_specifics[k]) do
  +
table.insert(config[k], v)
  +
end
  +
end
  +
end
  +
if class_specifics['defaults'] then
  +
for k, v in pairs(class_specifics['defaults']) do
  +
config['defaults'][k] = v
  +
end
  +
end
  +
end
  +
return config
  +
end
  +
  +
function s.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
  +
  +
function s.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(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
local tpl_args = getArgs(frame, {
 
  +
error(i18n.errors.missing_rarity)
parentFirst = true
 
})
+
end
frame = m_util.misc.get_frame(frame)
 
 
 
  +
where = string.format('%s AND items.class_id="%s" AND items.rarity_id="normal"', where, tpl_args.class_id)
--
 
-- Shared args
 
--
 
 
 
tpl_args._flags = {}
+
local join = {}
  +
tpl_args._base_item_args = {}
 
tpl_args._base_implicit_mods = {}
+
for _, table_name in ipairs(tpl_args._item_config.tables) do
  +
if table_name ~= 'items' then
tpl_args._defined_implicit_mods = {}
 
  +
join[#join+1] = string.format('items._pageID=%s._pageID', table_name)
tpl_args._mods = {}
 
for _, k in ipairs({'', '_random'}) do
 
for _, prefix in ipairs({'', '_implicit', '_explicit'}) do
 
tpl_args[k .. prefix .. '_stats'] = {}
 
 
end
 
end
 
end
 
end
tpl_args._subobjects = {}
 
tpl_args._properties = {}
 
tpl_args._errors = {}
 
 
 
  +
local fields = {
h.process_arguments(tpl_args, frame, {'class_id'})
 
  +
'items._pageName',
h.build_item_classes(tpl_args, frame)
 
  +
'items.name',
h.build_cargo_data(tpl_args, frame, c.item_classes)
 
  +
'items.metadata_id',
  +
}
 
 
  +
for _, k in ipairs(tpl_args._base_item_args) do
-- Using general purpose function to handle release and removal versions
 
  +
local data = core.map[k]
m_util.args.version(tpl_args, {frame=frame, set_properties=true})
 
  +
if data.field ~= nil then
  +
fields[#fields+1] = string.format('%s.%s', data.table, data.field)
  +
end
  +
end
 
 
  +
local result = m_cargo.query(
-- Must validate some argument early. It is required for future things
 
  +
tpl_args._item_config.tables,
h.process_arguments(tpl_args, frame, cfg.default_args)
 
  +
fields,
h.process_arguments(tpl_args, frame, c.item_classes[tpl_args.class_id].args)
 
  +
{
  +
where=where,
  +
join=table.concat(join, ','),
  +
groupBy='items._pageID',
  +
}
  +
)
 
 
-- Base Item
+
if #result > 1 then
  +
error(i18n.errors.duplicate_base_items)
h.process_base_item(tpl_args, frame)
 
  +
-- TODO be more explicit in the error?
  +
elseif #result == 0 then
  +
error(i18n.errors.base_item_not_found)
  +
end
 
 
  +
result = result[1]
-- Prophecy special snowflake
 
  +
if tpl_args._flags.is_prophecy then
 
  +
tpl_args.base_item_data = result
err = h.process_arguments(tpl_args, frame, cfg.prophecy_args)
 
  +
h.process_arguments(tpl_args, frame, {'base_item', 'base_item_page', 'base_item_id'})
if err then
 
  +
return err
 
  +
--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)]
  +
if value ~= nil then
  +
local default = type(data.default) == 'function' and data.default(tpl_args, frame) or data.default
  +
-- Inherit value from base item if derived item value is not equal to the default value.
  +
-- This verbose comparison is needed since two empty tables are not considered equal to one another.
  +
if tpl_args[k] == data.default or (type(tpl_args[k]) == 'table' and #tpl_args[k] == 0 and type(data.default) == 'table' and #data.default == 0) then
  +
tpl_args[k] = value
  +
if data.func ~= nil then
  +
tpl_args[k] = data.func(tpl_args, frame, tpl_args[k])
  +
end
  +
if data.func_copy ~= nil then
  +
data.func_copy(tpl_args, frame, tpl_args[k])
  +
end
  +
elseif tpl_args.debug then
  +
mw.log(string.format(i18n.debug.field_value_mismatch, k, tostring(tpl_args[k])))
  +
end
  +
elseif tpl_args.debug then
  +
mw.log(string.format(i18n.debug.base_item_field_not_found, data.table, data.field))
  +
end
  +
elseif data.func_fetch ~= nil then
  +
data.func_fetch(tpl_args, frame)
 
end
 
end
 
end
 
end
  +
end
 
  +
-- Mods
 
  +
function s.process_mods(tpl_args, frame)
 
for _, k in ipairs({'implicit', 'explicit'}) do
 
for _, k in ipairs({'implicit', 'explicit'}) do
 
local success = true
 
local success = true
Строка 2335: Строка 2534:
 
end
 
end
 
end
 
end
  +
 
  +
-- If the item does not have its own implicit mods, fall back to the implicit mods on the base item.
h.process_mods(tpl_args, frame)
 
  +
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']) * cfg.item_required_level_modifier_contribution)
  +
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.is_sellable == true and 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 core.map.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.is_sellable == true and 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 core.map.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 s.process_stats(tpl_args, frame)
 
-- Add stats - this is for when mods are not set, but we still need stats to calcuate new armour values etc
 
-- 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_'})
 
m_util.args.stats(tpl_args, {prefix='extra_'})
Строка 2374: Строка 2716:
 
 
 
-- Handle extra stats (for gems)
 
-- Handle extra stats (for gems)
 
 
if cfg.class_groups.gems.keys[tpl_args.class_id] then
 
if cfg.class_groups.gems.keys[tpl_args.class_id] then
m_skill._skill(tpl_args, frame)
+
h.skill(tpl_args, frame)
 
end
 
end
 
 
Строка 2388: Строка 2729:
 
for k, data in pairs(core.stat_map) do
 
for k, data in pairs(core.stat_map) do
 
local value = tpl_args[k]
 
local value = tpl_args[k]
 
 
if value ~= nil and skip[k] == nil then
 
if value ~= nil and skip[k] == nil then
 
value = {min=value, max=value, base=value}
 
value = {min=value, max=value, base=value}
Строка 2468: Строка 2808:
 
end
 
end
 
end
 
end
  +
end
   
  +
function s.process_weapon_dps(tpl_args, frame)
-- calculate and handle weapon dps
 
  +
for key, data in pairs(core.dps_map) do
if cfg.class_groups.weapons.keys[tpl_args.class_id] then
 
for _, data in ipairs(core.dps_map) do
+
local damage = {
local damage = {
+
min = {},
min = {},
+
max = {},
max = {},
+
}
}
+
for var_type, value in pairs(damage) do
  +
-- covers the min/max/avg range
 
for var_type, value in pairs(damage) do
+
for short_key, range_data in pairs(c.range_map) do
-- covers the min/max/avg range
+
value[short_key] = 0
for short_key, range_data in pairs(c.range_map) do
+
for _, damage_key in ipairs(data.damage_args) do
value[short_key] = 0
+
value[short_key] = value[short_key] + (tpl_args[string.format('%s_%s%s', damage_key, var_type, range_data.var)] or 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
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
  +
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, key, data.field, value, data.html_fmt_options or {})
 
end
 
end
 
end
 
end
  +
end
 
  +
-- late processing
 
h.process_arguments(tpl_args, frame, cfg.late_args) -- General late args
+
function s.get_categories(tpl_args, frame)
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('[[%s|%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('<br>[[%s|300px]]', tpl_args.skill_screenshot))
 
end
 
 
local out = tostring(infobox)
 
tpl_args.html_extra = out
 
 
-- ------------------------------------------------------------------------
 
-- Category handling
 
-- ------------------------------------------------------------------------
 
 
 
local cats = {}
 
local cats = {}
 
if tpl_args.rarity_id == 'unique' then
 
if tpl_args.rarity_id == 'unique' then
Строка 2598: Строка 2884:
 
cats[#cats+1] = i18n.categories.alternate_artwork
 
cats[#cats+1] = i18n.categories.alternate_artwork
 
end
 
end
 
-- TODO: add maintenance categories
 
 
 
 
if tpl_args.release_version == nil then
 
if tpl_args.release_version == nil then
Строка 2615: Строка 2899:
 
end
 
end
 
end
 
end
  +
  +
if tpl_args._flags.has_deprecated_skill_parameters then
  +
cats[#cats+1] = i18n.categories.deprecated_skill_parameters
  +
end
  +
return cats
  +
end
  +
  +
-- ----------------------------------------------------------------------------
  +
-- Exported functions
  +
-- ----------------------------------------------------------------------------
  +
  +
local p = {}
  +
  +
--
  +
-- Template:Item
  +
--
  +
function p.item(frame)
  +
local t = os.clock() -- Start time (for logging)
  +
  +
local tpl_args = getArgs(frame, {
  +
parentFirst = true
  +
})
  +
frame = m_util.misc.get_frame(frame)
  +
  +
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 = {}
  +
  +
-- Item configuration
  +
tpl_args._item_config = s.get_item_config(tpl_args, frame)
  +
  +
-- Add table names to core.map and append range fields.
  +
h.append_schema(tpl_args, frame, tpl_args._item_config.tables)
  +
  +
-- 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, tpl_args._item_config.args)
  +
  +
-- Base item
  +
s.process_base_item(tpl_args, frame)
  +
  +
-- Prophecies are considered currency items, but they have their own schema
  +
if tpl_args._flags.is_prophecy then
  +
h.process_arguments(tpl_args, frame, cfg.prophecy_args)
  +
end
  +
  +
-- Mods
  +
s.process_mods(tpl_args, frame)
  +
  +
-- Stats
  +
s.process_stats(tpl_args, frame)
  +
  +
-- Calculate and handle weapon dps
  +
if cfg.class_groups.weapons.keys[tpl_args.class_id] then
  +
s.process_weapon_dps(tpl_args, frame)
  +
end
  +
  +
-- Late argument processing
  +
h.process_arguments(tpl_args, frame, tpl_args._item_config.late_args)
  +
  +
-- Handle upgrade from restrictions/info
  +
h.process_upgraded_from(tpl_args, frame)
  +
  +
-- Quest reward info
  +
s.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:tag('span')
  +
:addClass('images')
  +
:wikitext(string.format(
  +
'[[%s|%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('<br>[[%s|300px]]', tpl_args.skill_screenshot))
  +
end
  +
  +
local out = tostring(infobox)
  +
tpl_args.html_extra = out
  +
  +
local cats = s.get_categories(tpl_args, frame)
 
 
out = out .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
+
out = out .. m_util.misc.add_category(cats, {ignore_blacklist=tpl_args.debug})
 
 
 
--
 
--
Строка 2632: Строка 3040:
   
 
if query_id then
 
if query_id then
out = out .. m_area.query_area_info{cats=yes, where=string.format('areas.id="%s"', query_id)}
+
out = out .. h.query_area_info{cats=true, where=string.format('areas.id="%s"', query_id)}
 
end
 
end
 
 
Строка 2639: Строка 3047:
 
-- ------------------------------------------------------------------------
 
-- ------------------------------------------------------------------------
 
-- Map argument values for cargo storage
 
-- Map argument values for cargo storage
for _, table_name in ipairs(c.item_classes[tpl_args.class_id].tables) do
+
for _, table_name in ipairs(tpl_args._item_config.tables) do
 
tpl_args._subobjects[table_name] = {
 
tpl_args._subobjects[table_name] = {
 
_table = table_name,
 
_table = table_name,
Строка 2651: Строка 3059:
 
v = tonumber(string.format("%.0f", v))
 
v = tonumber(string.format("%.0f", v))
 
if v ~= tpl_args[k] then
 
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))
+
mw.log(string.format('Float value "%s" for integer field "%s.%s"', tpl_args[k], data.table, data.field))
 
end
 
end
 
end
 
end
 
tpl_args._subobjects[data.table][data.field] = v
 
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
 
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)))
 
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)))
  +
elseif data.table == nil and data.field ~= nil then
  +
mw.log(string.format('Possibly redundant argument "%s", value:\n "%s"', k, mw.dumpObject(v)))
 
end
 
end
 
end
 
end
 
end
 
end
 
 
-- Don't actually save data in testing mode
+
-- Don't store cargo data in testing mode
 
if not tpl_args.test then
 
if not tpl_args.test then
 
local attach = {}
 
local attach = {}
Строка 2692: Строка 3100:
 
-- Show additional error messages in console to help fixing them
 
-- Show additional error messages in console to help fixing them
 
if #tpl_args._errors > 0 then
 
if #tpl_args._errors > 0 then
mw.logObject(table.concat(tpl_args._errors, '\n'))
+
mw.log(table.concat(tpl_args._errors, '\n'))
 
end
 
end
 
 
  +
if tpl_args.debug then
  +
mw.log(os.clock() - t)
  +
mw.log('Start logging tpl_args.')
  +
mw.logObject(tpl_args)
  +
mw.log('Stop logging tpl_args.')
  +
mw.log('Start logging core.map.')
  +
mw.logObject(core.map)
  +
mw.log('Stop logging core.map.')
  +
end
 
if tpl_args.test then
 
if tpl_args.test then
 
tpl_args.out = out
 
tpl_args.out = out
 
return tpl_args
 
return tpl_args
else
 
mw.logObject(os.clock() - t)
 
return out
 
 
end
 
end
  +
return out
 
end
 
end
   

Текущая версия на 06:12, 23 июля 2021

Документация модуля[просмотр] [править] [история] [очистить]

Предоставляет функциональные возможности для различных шаблонов, связанных с предметами.

Описание

This module is responsible for creating item boxes, various item lists, item links and other item-related tasks. In the process a lot of the input data is verified and also added as semantic property to pages; as such, any templates deriving from this module should not be used on user pages other then for temporary testing purposes.

This template is also backed by an export script in PyPoE which can be used to export item data from the game files which then can be used on the wiki. Use the export when possible,

Шаблоны Item

Модуль:Item2

Все шаблоны, определенные в Модуль:Item2:

Модуль:Item table

Все шаблоны, определенные в Модуль:Item table:

Модуль:Item link

Все шаблоны, определенные в Модуль:Item link:

Модуль:Item acquisition

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

require('Module:No globals')
local getArgs = require('Module:Arguments').getArgs
local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')

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

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

-- Lazy loading
local f_skill -- require('Module:Skill')._skill
local f_item_link -- require('Module:Item link').item_link
local f_query_area_info -- require('Module:Area').query_area_info
local f_process_upgraded_from -- require('Module:Item2/upgrade').process_upgraded_from
local f_append_schema -- require('Module:Item2/cargo').append_schema

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

-- Declare early to avoid errors
local c = {}

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

local h = {}

-- Lazy loading for Module:Skill
function h.skill(tpl_args, frame)
    if not f_skill then
        f_skill = use_sandbox and require('Module:Skill/sandbox')._skill or require('Module:Skill')._skill
    end
    return f_skill(tpl_args, frame)
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

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

-- Lazy loading for Module:Item2/cargo
function h.append_schema(tpl_args, frame, tables)
    if not f_append_schema then
        f_append_schema = use_sandbox and require('Module:Item2/cargo/sandbox').append_schema or require('Module:Item2/cargo').append_schema
    end
    return f_append_schema(tpl_args, frame, tables)
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

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)
    tpl_args._processed_args = tpl_args._processed_args or {}
    for _, k in ipairs(params) do
        local data = core.map[k]
        if data == nil then
            if tpl_args.debug then
                error(string.format(i18n.debug.invalid_argument_key, k))
            end
        else
            if data.inherit ~= false then
                table.insert(tpl_args._base_item_args, k)
            end
            if data.func ~= nil then
                tpl_args[k] = data.func(tpl_args, frame, tpl_args[k])
            end
            if tpl_args[k] == nil then
                if tpl_args.class_id and tpl_args._item_config.defaults ~= nil and tpl_args._item_config.defaults[k] ~= nil then
                    -- Item defaults
                    tpl_args[k] = tpl_args._item_config.defaults[k]
                elseif data.default ~= nil then
                    -- Generic defaults
                    if type(data.default) == 'function' then
                        tpl_args[k] = data.default(tpl_args, frame)
                    else
                        tpl_args[k] = data.default
                    end
                end
            end
            tpl_args._processed_args[k] = true
        end
    end
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, '<th class="mw%-customtoggle%-31">(.+)</th>')
            if text ~= nil then
                stat_text = string.gsub(stat_text, '<table class="random%-modifier%-stats.+</table>', 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('<em class="header">%s</em><br>', group.heading)
            end
            statcont
                :tag('span')
                :attr('class', 'group ' .. (group.class or ''))
                :wikitext(heading .. table.concat(lines, '<br>'))
                :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( '[[' .. tpl_args.card_art .. '|link=|alt=]]' )
                    :done()
                :tag('span')
                    :attr('class', 'divicard-frame')
                    :wikitext( '[[File:Divination card frame.png|link=|alt=]]' )
                    :done()
                :tag('span')
                    :attr('class', 'divicard-header')
                    :wikitext(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
        local line_type
        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 .. '<br>' .. 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.maybe_show_infobox_line(args)
    -- Verifies that a list of keys are present in tpl_args and that the item 
    -- should use those keys. Returns true if all conditions are met, otherwise 
    -- returns false.
    return function (tpl_args)
        local keys = args.keys or {}
        local conditions = args.conditions or {}
        if #keys == 0 then
            return false
        end
        for _, k in ipairs(keys) do
            if tpl_args[k] == nil then
                return false
            end
            local t = type(tpl_args[k])
            if (t == 'table' or t == 'string') and #tpl_args[k] == 0 then
                return false
            end
            if conditions.processed == true and tpl_args._processed_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.maybe_show_infobox_line{
                keys = {'cosmetic_type'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'gem_tags'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'support_gem_letter_html'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'support_gem_letter_html',
                    },
                },
                fmt = i18n.tooltips.support_icon,
            },
        },
        {
            show = function (tpl_args, frame)
                return tpl_args.skill_levels and cfg.class_groups.gems.keys[tpl_args.class_id]
            end,
            func = function (tpl_args, frame)
                local value = {
                    base = 1,
                    min = 1,
                    max = tpl_args.max_level,
                }
                local options = {
                    color = 'value',
                }
                return string.format(
                    i18n.tooltips.level,
                    m_util.html.format_value(tpl_args, frame, value, options)
                )
            end,
        },
        {
            show = function (tpl_args, frame)
                return tpl_args.skill_costs
            end,
            func = function (tpl_args, frame)
                local parts = {}
                if not tpl_args.skill_costs.has_spending_cost then
                    -- Try falling back to deprecated parameters
                    if not tpl_args.has_reservation_mana_cost then
                        parts[1] = {
                            key = 'mana_cost',
                            fmt = '%i',
                            color = false,
                            inline = string.format('%%s %s', m_game.constants.skill.cost_types.mana.long_upper),
                            inline_color = 'value',
                        }
                    end
                else
                    for i=1, #tpl_args.skill_costs do
                        if not tpl_args.skill_costs[i].is_reservation then -- Only get spending costs
                            local cost_type = tpl_args.skill_costs[i].type
                            parts[#parts+1] = {
                                key = {'costs', i, 'amount'},
                                fmt = '%i',
                                color = false,
                                inline = string.format(
                                    '%%s%s %s',
                                    string.find(cost_type, 'percent', 1, true) and '%%' or '',
                                    m_game.constants.skill.cost_types[cost_type].long_upper
                                ),
                                inline_color = 'value',
                            }
                        end
                    end
                end
                return core.factory.infobox_line{
                    type = 'gem',
                    parts = parts,
                    sep = ', ',
                    fmt = i18n.tooltips.cost,
                }(tpl_args, frame)
            end,
        },
        {
            show = function (tpl_args, frame)
                return tpl_args.skill_costs
            end,
            func = function (tpl_args, frame)
                if not tpl_args.skill_costs then
                    return
                end
                local parts = {}
                if not tpl_args.skill_costs.has_reservation_cost then
                    -- Try falling back to deprecated parameters
                    if tpl_args.has_reservation_mana_cost then
                        parts[1] = {
                            key = 'mana_cost',
                            fmt = '%i',
                            color = false,
                            inline = string.format(
                                '%%s%s %s',
                                tpl_args.has_percentage_mana_cost and '%%' or '',
                                m_game.constants.skill.cost_types.mana.long_upper
                            ),
                            inline_color = 'value',
                        }
                    end
                else
                    for i=1, #tpl_args.skill_costs do
                        if tpl_args.skill_costs[i].is_reservation then -- Only get reservation costs
                            local cost_type = tpl_args.skill_costs[i].type
                            parts[#parts+1] = {
                                key = {'costs', i, 'amount'},
                                fmt = '%i',
                                color = false,
                                inline = string.format(
                                    '%%s%s %s',
                                    string.find(cost_type, 'percent', 1, true) and '%%' or '',
                                    m_game.constants.skill.cost_types[cost_type].long_upper
                                ),
                                inline_color = 'value',
                            }
                        end
                    end
                end
                return core.factory.infobox_line{
                    type = 'gem',
                    parts = parts,
                    sep = ', ',
                    fmt = i18n.tooltips.reservation,
                }(tpl_args, frame)
            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',
                        color = false,
                        inline = '%s%%',
                        inline_color = 'value',
                    },
                },
                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 = function (tpl_args, frame)
                return tpl_args.cast_time and tpl_args.gem_tags and (m_util.table.contains(tpl_args.gem_tags, m_game.constants.item.gem_tags.spell.tag) or m_util.table.contains(tpl_args.gem_tags, m_game.constants.item.gem_tags.warcry.tag))
            end,
            func = function (tpl_args, frame)
                return 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 = m_util.table.contains(tpl_args.gem_tags, m_game.constants.item.gem_tags.spell.tag) and i18n.tooltips.cast_time or i18n.tooltips.use_time,
                }(tpl_args, frame)
            end,
        },
        {
            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',
                        color = false,
                        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',
                        color = false,
                        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',
                        color = false,
                        inline = '%s%%',
                        inline_color = 'value',
                    },
                },
                fmt = i18n.tooltips.damage_effectiveness,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'projectile_speed'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'projectile_speed',
                    },
                },
                fmt = i18n.tooltips.projectile_speed,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'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,
            },
        },
        -- Quality is before item stats, but after gem stuff and item class
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'quality'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'quality',
                        fmt = '+%i%%',
                        color = 'mod',
                        hide_default = 0,
                    },
                },
                fmt = i18n.tooltips.quality,
            },
        },
        -- Weapon only
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'physical_damage_html'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'chaos_damage_html'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'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.maybe_show_infobox_line{
                keys = {'attack_speed_html'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'attack_speed_html',
                        fmt = '%s',
                    },
                },
                fmt = i18n.tooltips.attacks_per_second,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'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.maybe_show_infobox_line{
                keys = {'map_area_level'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'map_area_level',
                        fmt = '%i',
                    },
                },
                fmt = i18n.tooltips.map_level,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'map_tier'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'jewel_radius_html'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'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.maybe_show_infobox_line{
                keys = {'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.maybe_show_infobox_line{
                keys = {'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.maybe_show_infobox_line{
                keys = {'buff_stat_text'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'buff_stat_text',
                        color = 'mod',
                    },
                },
            },
        },
        -- Armor only
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'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.maybe_show_infobox_line{
                keys = {'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.maybe_show_infobox_line{
                keys = {'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.maybe_show_infobox_line{
                keys = {'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.maybe_show_infobox_line{
                keys = {'ward_html'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'ward_html',
                        fmt = '%s',
                        hide_default = 0,
                        hide_default_key = 'ward',
                    },
                },
                fmt = i18n.tooltips.ward,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'movement_speed'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'movement_speed',
                        fmt = '%s%%',
                        hide_default = 0,
                    },
                },
                fmt = i18n.tooltips.movement_speed,
            },
        },
        -- Amulet only
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'talisman_tier'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'talisman_tier',
                        fmt = '%i',
                    },
                },
                fmt = i18n.tooltips.talisman_tier,
            },
        },
        -- Misc
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'stack_size'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'stack_size',
                        fmt = '%i',
                        hide_default = 1,
                    },
                },
                fmt = i18n.tooltips.stack_size,
            },
        },
        -- Essence stuff
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'essence_level'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'essence_level',
                        fmt = '%i',
                    },
                },
                fmt = i18n.tooltips.essence_level,
            },
        },
        -- Blight items
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'blight_item_tier'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'seed_tier'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'seed_tier',
                        fmt = '%i',
                    },
                },
                fmt = i18n.tooltips.seed_tier,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'seed_tier'},
                conditions = {processed = true},
            },
            func = function (tpl_args, value)
                return i18n.tooltips.seed_monster
            end,
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'seed_type_html'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'seed_type_html',
                        fmt = '%s',
                    },
                },
                fmt = i18n.tooltips.seed_lifeforce_gained,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'seed_growth_cycles'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'seed_growth_cycles',
                        fmt = '%s',
                    },
                },
                fmt = i18n.tooltips.seed_growth_cycles,
            },
        },
        -- Heist
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'heist_required_npcs'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'heist_required_npcs',
                        fmt = '%s',
                    },
                },
                fmt = i18n.tooltips.heist_required_npc,
            },
        },
    },
    -- Requirements
    {
        -- 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 = function (tpl_args, frame, value)
                            local min = m_game.constants.characters.minimum_attributes[m_game.constants.attributes[attr].arg]
                            return value.min <= min and value.max <= min
                        end,
                        hide_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.maybe_show_infobox_line{
                keys = {'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.maybe_show_infobox_line{
                keys = {'gem_description'},
            },
            func = h.factory.display_raw_value('gem_description'),
        },
    },
    -- Gem Quality Stats
    {
        class = 'tc -mod',
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'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)
                local 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, '<br>')
            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.maybe_show_infobox_line{
                keys = {'experience'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'experience',
                        fmt = '%i',
                    },
                },
            },
        },
    },]]--
    -- Harvest seeds (lower section)
    {
        class = 'tc -mod',
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'seed_consumed_wild_lifeforce_percentage'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'seed_consumed_vivid_lifeforce_percentage'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'seed_consumed_primal_lifeforce_percentage'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'seed_required_nearby_seed_tier', 'seed_type_html', 'seed_required_nearby_seed_amount'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'seed_effect'},
                conditions = {processed = true},
            },
            func = h.factory.display_raw_value('seed_effect'),
        },
    },
    -- Description (currency, doodads)
    {
        class = 'tc -mod',
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'description'},
                conditions = {processed = true},
            },
            func = h.factory.display_raw_value('description'),
        },
        --[[{
            show = h.factory.maybe_show_infobox_line{
                keys = {'plant_booster_additional_crafting_options'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'plant_booster_extra_chances'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'plant_booster_extra_chances',
                        fmt = '%s%%',
                    },
                },
                fmt = i18n.tooltips.plant_booster_extra_chances,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'plant_booster_lifeforce'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'plant_booster_lifeforce',
                        fmt = '%s%%',
                    },
                },
                fmt = i18n.tooltips.plant_booster_lifeforce,
            },
        },]]
    },
    {
        class = 'tc -crafted',
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'incubator_effect'},
                conditions = {processed = true},
            },
            func = h.factory.display_raw_value('incubator_effect'),
        },
    },
    -- Variations (for doodads)
    {
        class = 'tc -mod',
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'variation_count'},
                conditions = {processed = true},
            },
            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('%s %i', txt, tpl_args.variation_count)
            end,
        },
    },
    -- Flavour Text
    {
        class = 'tc -flavour',
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'flavour_text'},
                conditions = {processed = true},
            },
            func = h.factory.display_raw_value('flavour_text'),
        },
    },
    -- Prophecy text
    {
        class = 'tc -value',
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'prediction_text'},
                conditions = {processed = true},
            },
            func = h.factory.display_raw_value('prediction_text'),
        },
    },
    -- Can not be traded or modified
    {
        class = 'tc -canttradeormodify',
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'cannot_be_traded_or_modified'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'help_text'},
                conditions = {processed = true},
            },
            func = h.factory.display_raw_value('help_text'),
        },
    },
}

--
-- 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.maybe_show_infobox_line{
                keys = {'atlas_connections'},
                conditions = {processed = true},
            },
            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
        },
    },
    -- Acquisition
    {
        heading = i18n.tooltips.acquisition,
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'drop_level'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'drop_level',
                        fmt = '%i',
                        
                    },
                    {
                        key = 'drop_level_maximum',
                        fmt = '%i',
                        hide_default = 100,
                    },
                },
                sep = ' / ',
                fmt = i18n.tooltips.area_level,
            },
        },
        {
            show = function (tpl_args, frame)
                if tpl_args.drop_enabled == false then
                    return true
                end
                return false
            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,
        },
        {
            show = function (tpl_args, frame)
                if tpl_args.is_drop_restricted == true and tpl_args.drop_enabled ~= false then
                    return true
                end
                return false
            end,
            func = function (tpl_args, frame)
                local span = mw.html.create('span')
                span
                    :attr('class', 'infobox-restricted-drop')
                    :wikitext(i18n.tooltips.drop_restricted)
                    :done()
                return tostring(span)
            end,
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'drop_leagues'},
                conditions = {processed = true},
            },
            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.maybe_show_infobox_line{
                keys = {'drop_areas_html'},
                conditions = {processed = true},
            },
            func = h.factory.display_raw_value('drop_areas_html'),
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'drop_text'},
                conditions = {processed = true},
            },
            func = h.factory.display_raw_value('drop_text'),
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'master', 'master_level_requirement'},
                conditions = {processed = true},
            },
            func = function (tpl_args, frame)
                local master
                for _, v in ipairs(m_game.constants.masters) do
                    if tpl_args.master == v.full then
                        master = v
                        break
                    end
                end
                local text = string.format('[[%s|%s]] ', master.full, master.short_upper) .. string.format(i18n.tooltips.master_level, tpl_args.master_level_requirement)
                return string.format(i18n.tooltips.master, m_util.html.poe_color('value', text))
            end
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'master', 'master_favour_cost'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'master_favour_cost',
                        color = 'currency',
                        hide_default = 0,
                    },
                },
                fmt = i18n.tooltips.favour_cost,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'seal_cost'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'seal_cost',
                        fmt = function (tpl_args, frame, value)
                            return string.format('%s %s', i18n.fmt.item_count, h.item_link{metadata_id='Metadata/Items/Currency/CurrencySilverCoin', html=''})
                        end,
                        color = 'currency',
                    },
                },
                fmt = i18n.tooltips.seal_cost,
            },
        },
    },
    {
        -- Vendor prices
        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]]<br />', purchase_data.amount, purchase_data.name))
                        end
                    end
                end
                
                return tostring(tbl)
            end,
        },
    },
    {
        -- Vendor offer
        heading = i18n.tooltips.sell_price,
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'sell_price_order'},
            },
            func = function (tpl_args, frame)
                local out = {}
                if #tpl_args.sell_price_order > 0 then
                    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
                end
                return table.concat(out, '<br />')
            end,
        },
    },
    -- Damage per second
    {
        heading = i18n.tooltips.damage_per_second,
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'physical_dps_html'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'physical_dps_html',
                        fmt = '%s',
                        color = false, -- the html already contains the colour
                    },
                },
                fmt = i18n.tooltips.physical_dps,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'fire_dps_html'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'fire_dps_html',
                        fmt = '%s',
                        color = false, -- the html already contains the colour
                    },
                },
                fmt = i18n.tooltips.fire_dps,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'cold_dps_html'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'cold_dps_html',
                        fmt = '%s',
                        color = false, -- the html already contains the colour
                    },
                },
                fmt = i18n.tooltips.cold_dps,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'lightning_dps_html'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'lightning_dps_html',
                        fmt = '%s',
                        color = false, -- the html already contains the colour
                    },
                },
                fmt = i18n.tooltips.lightning_dps,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'chaos_dps_html'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'chaos_dps_html',
                        fmt = '%s',
                        color = false, -- the html already contains the colour
                    },
                },
                fmt = i18n.tooltips.chaos_dps,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'elemental_dps_html'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'elemental_dps_html',
                        fmt = '%s',
                        color = false, -- the html already contains the colour
                    },
                },
                fmt = i18n.tooltips.elemental_dps,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'poison_dps_html'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'poison_dps_html',
                        fmt = '%s',
                        color = false, -- the html already contains the colour
                    },
                },
                fmt = i18n.tooltips.poison_dps,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'dps_html'},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'dps_html',
                        fmt = '%s',
                        color = false, -- the html already contains the colour
                    },
                },
                fmt = i18n.tooltips.dps,
            },
        },
    },
    {
        heading = i18n.tooltips.metadata,
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'class'},
                conditions = {processed = true},
            },
            func = core.factory.infobox_line{
                parts = {
                    {
                        key = 'class',
                        fmt = '%s',
                    },
                },
                fmt = i18n.tooltips.item_class,
            },
        },
        {
            show = h.factory.maybe_show_infobox_line{
                keys = {'metadata_id'},
                conditions = {processed = true},
            },
            func = function (tpl_args, frame)
                return core.factory.infobox_line{
                    parts = {
                        {
                            key = 'metadata_id',
                            fmt = '%s',
                            inline = m_util.html.abbr('%s', tpl_args.metadata_id),
                        },
                    },
                    fmt = tostring(
                        mw.html.create('span')
                            :addClass('u-truncate-line')
                            :wikitext(i18n.tooltips.metadata_id)
                    ),
                }(tpl_args, frame)
            end,
        },
    },
}

-- ----------------------------------------------------------------------------
-- Subroutines
-- ----------------------------------------------------------------------------

local s = {}

-- Subroutines for p.item. These exist to declutter the main function. Each one of these is only called once.

function s.get_item_config(tpl_args, frame)
    -- Returns primary item configuration, based on item class

    local config = {
        tables = {}, -- Table names that data may be stored to
        args = {}, -- Args to process early
        late_args = {}, -- Args to process late
        defaults = {}, -- Defaults based on item class
    }
    for _, v in ipairs(cfg.tables) do
        table.insert(config.tables, v)
    end
    for _, v in ipairs(cfg.default_args) do
        table.insert(config.args, v)
    end
    for _, v in ipairs(cfg.late_args) do
        table.insert(config.late_args, v)
    end

    -- Process class_id
    h.process_arguments(tpl_args, frame, {'class_id'})

    local extend_keys = {'tables', 'args', 'late_args'} -- We will do defaults separately to preserve order of args
    for _, row in pairs(cfg.class_groups) do
        if row.keys[tpl_args.class_id] then
            for _, k in ipairs(extend_keys) do
                if row[k] then
                    for _, v in ipairs(row[k]) do
                        table.insert(config[k], v)
                    end
                end
            end
            if row['defaults'] then
                for k, v in pairs(row['defaults']) do
                    config['defaults'][k] = v
                end
            end
            break
        end
    end
    local class_specifics = cfg.class_specifics[tpl_args.class_id]
    if class_specifics then
        for _, k in ipairs(extend_keys) do
            if class_specifics[k] then
                for _, v in ipairs(class_specifics[k]) do
                    table.insert(config[k], v)
                end
            end
        end
        if class_specifics['defaults'] then
            for k, v in pairs(class_specifics['defaults']) do
                config['defaults'][k] = v
            end
        end
    end
    return config
end

function s.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

function s.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(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(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(tpl_args._item_config.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(
        tpl_args._item_config.tables,
        fields,
        {
            where=where,
            join=table.concat(join, ','),
            groupBy='items._pageID',
        }
    )
    
    if #result > 1 then
        error(i18n.errors.duplicate_base_items)
        -- TODO be more explicit in the error?
    elseif #result == 0 then
        error(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)]
            if value ~= nil then
                local default = type(data.default) == 'function' and data.default(tpl_args, frame) or data.default
                -- Inherit value from base item if derived item value is not equal to the default value.
                -- This verbose comparison is needed since two empty tables are not considered equal to one another.
                if tpl_args[k] == data.default or (type(tpl_args[k]) == 'table' and #tpl_args[k] == 0 and type(data.default) == 'table' and #data.default == 0) then
                    tpl_args[k] = value
                    if data.func ~= nil then
                        tpl_args[k] = data.func(tpl_args, frame, tpl_args[k])
                    end
                    if data.func_copy ~= nil then
                        data.func_copy(tpl_args, frame, tpl_args[k])
                    end
                elseif tpl_args.debug then
                    mw.log(string.format(i18n.debug.field_value_mismatch, k, tostring(tpl_args[k])))
                end
            elseif tpl_args.debug then
                mw.log(string.format(i18n.debug.base_item_field_not_found, data.table, data.field))
            end
        elseif data.func_fetch ~= nil then
            data.func_fetch(tpl_args, frame)
        end
    end
end

function s.process_mods(tpl_args, frame)
    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

    -- 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']) * cfg.item_required_level_modifier_contribution)
                    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.is_sellable == true and 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 core.map.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.is_sellable == true and 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 core.map.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 s.process_stats(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
        h.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
end

function s.process_weapon_dps(tpl_args, frame)
    for key, data in pairs(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, key, data.field, value, data.html_fmt_options or {})
        end
    end
end

function s.get_categories(tpl_args, frame)
    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.class, tpl_args.map_series)
    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', tpl_args.class, m_game.constants.attributes[attr].long_lower_2)
        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
    
    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

    if tpl_args._flags.has_deprecated_skill_parameters then
        cats[#cats+1] = i18n.categories.deprecated_skill_parameters
    end
    return cats
end

-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------

local p = {}

--
-- Template:Item
--
function p.item(frame)
    local t = os.clock() -- Start time (for logging)
    
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    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 = {}
    
    -- Item configuration
    tpl_args._item_config = s.get_item_config(tpl_args, frame)

    -- Add table names to core.map and append range fields.
    h.append_schema(tpl_args, frame, tpl_args._item_config.tables)
    
    -- 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, tpl_args._item_config.args)
    
    -- Base item
    s.process_base_item(tpl_args, frame)
    
    -- Prophecies are considered currency items, but they have their own schema
    if tpl_args._flags.is_prophecy then
        h.process_arguments(tpl_args, frame, cfg.prophecy_args)
    end
    
    -- Mods
    s.process_mods(tpl_args, frame)

    -- Stats
    s.process_stats(tpl_args, frame)

    -- Calculate and handle weapon dps
    if cfg.class_groups.weapons.keys[tpl_args.class_id] then
        s.process_weapon_dps(tpl_args, frame)
    end
    
    -- Late argument processing
    h.process_arguments(tpl_args, frame, tpl_args._item_config.late_args)
    
    -- Handle upgrade from restrictions/info
    h.process_upgraded_from(tpl_args, frame)
    
    -- Quest reward info
    s.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:tag('span')
            :addClass('images')
            :wikitext(string.format(
                '[[%s|%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('<br>[[%s|300px]]', tpl_args.skill_screenshot))
    end
    
    local out = tostring(infobox)
    tpl_args.html_extra = out
    
    local cats = s.get_categories(tpl_args, frame)
    
    out = out .. m_util.misc.add_category(cats, {ignore_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 .. h.query_area_info{cats=true, where=string.format('areas.id="%s"', query_id)}
    end
    
    -- ------------------------------------------------------------------------
    -- Store cargo data
    -- ------------------------------------------------------------------------
    -- Map argument values for cargo storage
    for _, table_name in ipairs(tpl_args._item_config.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.log(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 field for table "%s", key "%s", \nvalue:\n "%s" \ndata:\n%s', data.table, k, mw.dumpObject(v), mw.dumpObject(data)))
            elseif data.table == nil and data.field ~= nil then
                mw.log(string.format('Possibly redundant argument "%s", value:\n "%s"', k, mw.dumpObject(v)))
            end
        end
    end
    
    -- Don't store cargo 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.log(table.concat(tpl_args._errors, '\n'))
    end
    
    if tpl_args.debug then
        mw.log(os.clock() - t)
        mw.log('Start logging tpl_args.')
        mw.logObject(tpl_args)
        mw.log('Stop logging tpl_args.')
        mw.log('Start logging core.map.')
        mw.logObject(core.map)
        mw.log('Stop logging core.map.')
    end
    if tpl_args.test then
        tpl_args.out = out
        return tpl_args
    end
    return out
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