Módulo:Item: mudanças entre as edições
Aspeto
Sem resumo de edição |
Sem resumo de edição |
||
(18 revisões intermediárias por 2 usuários não estão sendo mostradas) | |||
Linha 120: | Linha 120: | ||
-- Processing | -- Processing | ||
-- | -- | ||
function h.contains(table, element) | |||
for _, value in ipairs(table) do | |||
if value == element then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
-- Uso: | |||
function h.process_arguments(tpl_args, params) | function h.process_arguments(tpl_args, params) | ||
Linha 126: | Linha 138: | ||
for _, k in ipairs(params) do | for _, k in ipairs(params) do | ||
local arg_def = core.map[k] | local arg_def = core.map[k] | ||
if arg_def == nil then | if arg_def == nil then | ||
if tpl_args.debug then | if tpl_args.debug then | ||
Linha 2 620: | Linha 2 633: | ||
function s.process_base_item(tpl_args) | function s.process_base_item(tpl_args) | ||
-- error(debug_print.table_to_string(tpl_args)) | |||
if not tpl_args._flags.is_derived then | if not tpl_args._flags.is_derived then | ||
if tpl_args.rarity_id ~= 'normal' then | if tpl_args.rarity_id ~= 'normal' then | ||
Linha 2 658: | Linha 2 673: | ||
}, qargs) | }, qargs) | ||
if result.error then | if result.error then | ||
-- error(debug_print.table_to_string(result)) | |||
result.error:throw(true) | result.error:throw(true) | ||
end | end | ||
if result['items.rarity_id'] ~= 'normal' then | if result['items.rarity_id'] ~= 'normal' then | ||
error(debug_print.table_to_string(result)) | -- error(debug_print.table_to_string(result)) | ||
error(i18n.errors.base_item_wrong_rarity) | error(i18n.errors.base_item_wrong_rarity) | ||
end | end | ||
Linha 3 082: | Linha 3 098: | ||
local value = {} | local value = {} | ||
for short_key, range_def in pairs(c.range_map) do | for short_key, range_def in pairs(c.range_map) do | ||
local | local min_damage = damage.min[short_key] | ||
local max_damage = damage.max[short_key] | |||
local a = string.format('attack_speed%s', range_def) | |||
-- error(range_def) | |||
local atack_spd = tpl_args[a] or 1 | |||
-- error(debug_print.table_to_string(tpl_args)) | |||
-- error(debug_print.table_to_string(range_def)) | |||
local result = (min_damage + max_damage) / 2 * atack_spd | |||
value[short_key] = result | value[short_key] = result | ||
tpl_args[string.format('%s%s', dps_def.field, range_def)] = result | tpl_args[string.format('%s%s', dps_def.field, range_def)] = result |
Edição atual tal como às 02h50min de 30 de março de 2025
The module implements {{item}}.
Overview
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.
-------------------------------------------------------------------------------
--
-- Module:Item
--
-- This module implements Template:Item.
-------------------------------------------------------------------------------
_pageName='{{#replace:{{PAGENAME}}|'|\'}}'
require('Module:No globals')
local m_util = require('Module:Util')
local m_item_util -- Lazy load require('Module:Item util')
local m_cargo = require('Module:Cargo')
local debug_print = require('Module:Print')
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Item')
local m_game = use_sandbox and mw.loadData('Module:Game/sandbox') or mw.loadData('Module:Game')
-- Lazy loading
local f_process_skill_data -- require('Module:Skill').process_skill_data
local f_item_link -- require('Module:Item link').item_link
local f_query_area_info -- require('Module:Area').query_area_info
local f_process_recipes -- require('Module:Item/recipes').process_recipes
local f_append_schema -- require('Module:Item/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:Item/config/sandbox') or mw.loadData('Module:Item/config')
local i18n = cfg.i18n
local core = use_sandbox and require('Module:Item/core/sandbox') or require('Module:Item/core')
-- Declare early to avoid errors
local c = {}
-- ----------------------------------------------------------------------------
-- Helper functions
-- ----------------------------------------------------------------------------
local h = {}
-- Lazy loading for Module:Skill
function h.process_skill_data(tpl_args)
if not f_process_skill_data then
f_process_skill_data = use_sandbox and require('Module:Skill/sandbox').process_skill_data or require('Module:Skill').process_skill_data
end
return f_process_skill_data(tpl_args)
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:Area
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:Item/cargo
function h.append_schema(tpl_args, tables)
if not f_append_schema then
f_append_schema = use_sandbox and require('Module:Item/cargo/sandbox').append_schema or require('Module:Item/cargo').append_schema
end
return f_append_schema(tpl_args, tables)
end
-- Lazy loading for Module:Item/recipes
function h.process_recipes(tpl_args)
if not f_process_recipes then
f_process_recipes = use_sandbox and require('Module:Item/recipes/sandbox').process_recipes or require('Module:Item/recipes').process_recipes
end
return f_process_recipes(tpl_args)
end
function h.handle_range_args(tpl_args, key, field, value, options)
local opt = {}
for k, v in pairs(options or {}) do
opt[k] = v
end
local html, val2 = m_util.html.format_value(tpl_args, value, opt)
tpl_args[key .. '_html'] = html
tpl_args[field .. '_html'] = html
tpl_args[field .. '_range_colour'] = val2.color
opt.color = false
tpl_args[field .. '_range_text'] = m_util.html.format_value(tpl_args, value, opt)
end
h.stat = {}
function h.stat.add(value, magnitude)
value.min = value.min + magnitude.min
value.max = value.max + magnitude.max
end
function h.stat.add_distance(value, magnitude)
-- Stats for distance are 10x the display value
value.min = value.min + (magnitude.min / 10)
value.max = value.max + (magnitude.max / 10)
end
function h.stat.increased(value, magnitude)
value.min = value.min * (1 + magnitude.min / 100)
value.max = value.max * (1 + magnitude.max / 100)
end
function h.stat.increased_inverse(value, magnitude)
value.min = value.min / (1 + magnitude.min / 100)
value.max = value.max / (1 + magnitude.max / 100)
end
--
-- Processing
--
function h.contains(table, element)
for _, value in ipairs(table) do
if value == element then
return true
end
end
return false
end
-- Uso:
function h.process_arguments(tpl_args, params)
tpl_args._processed_args = tpl_args._processed_args or {}
tpl_args._base_item_args = tpl_args._base_item_args or {}
for _, k in ipairs(params) do
local arg_def = core.map[k]
if arg_def == nil then
if tpl_args.debug then
error(string.format(i18n.debug.invalid_argument_key, k))
end
else
if arg_def.inherit ~= false then
table.insert(tpl_args._base_item_args, k)
end
if tpl_args[k] ~= nil and arg_def.deprecated == true then
tpl_args._flags.uses_deprecated_parameters = true
tpl_args._deprecated_args = tpl_args._deprecated_args or {}
table.insert(tpl_args._deprecated_args, k)
end
if arg_def.func ~= nil then
tpl_args[k] = arg_def.func(tpl_args, tpl_args[k])
end
if tpl_args[k] == nil then
if tpl_args.class_id and tpl_args._item_config.defaults[k] ~= nil and (not tpl_args._flags.is_derived or arg_def.inherit == false) then
-- Defaults based on item class
-- Ignore inherited fields if this is a derived item; these are set by the base item instead
tpl_args[k] = tpl_args._item_config.defaults[k]
elseif arg_def.default ~= nil then
-- General defaults
if type(arg_def.default) == 'function' then
tpl_args[k] = arg_def.default(tpl_args)
else
tpl_args[k] = arg_def.default
end
end
end
tpl_args._processed_args[k] = true
end
end
end
--
-- Display
--
function h.strip_random_stats(tpl_args, stat_text)
if tpl_args._flags.random_mods 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_infobox_from_map(tpl_args, infobox, mapping)
local statcont = mw.html.create('span')
:addClass('item-stats')
local count = 0 -- Number of groups in infobox
for _, group in ipairs(mapping) do
local lines = {}
for _, line in ipairs(group) do
local show = true
if line.show == false then
show = false
elseif type(line.show) == 'function' then
show = line.show(tpl_args)
end
if show then
lines[#lines+1] = line.func(tpl_args)
end
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')
:addClass('group ' .. (group.class or ''))
:wikitext(heading .. table.concat(lines, '<br>'))
:done()
end
end
-- Add groups to infobox
if count > 0 then
infobox:node(statcont)
end
end
function h.make_main_infobox(tpl_args)
local infobox = mw.html.create('span')
:addClass('item-box -' .. tpl_args.frame_type)
if tpl_args.class_id == 'DivinationCard' then
local divcard = infobox
:tag('span')
:addClass('divicard-wrapper')
local card_background = tpl_args.card_background or {}
if #card_background > 0 then
divcard
:tag('span')
:addClass('divicard-background')
:wikitext(string.format(
'[[%s|390px|gif]]',
string.format(
i18n.files.divination_card_background,
table.concat(card_background, '-')
)
))
end
divcard
:tag('span')
:addClass('divicard-art')
:wikitext(string.format('[[%s|link=|alt=]]', tpl_args.card_art))
:done()
:tag('span')
:addClass('divicard-frame')
:done()
:tag('span')
:addClass('divicard-header')
:wikitext(tpl_args.name)
:done()
:tag('span')
:addClass('divicard-artlink')
:wikitext(string.format('[[%s]]', tpl_args.card_art))
:done()
:tag('span')
:addClass('divicard-stack')
:wikitext(tpl_args.stack_size)
:done()
:tag('span')
:addClass('divicard-face')
:tag('span')
:addClass('divicard-reward')
:wikitext(tpl_args.description)
:done()
:tag('span')
:addClass('divicard-flavour')
:wikitext(tpl_args.flavour_text)
:done()
else
local line_type
local name_line
if tpl_args._flags.is_derived and tpl_args.rarity_id ~= 'normal' then
line_type = 'double'
name_line = tpl_args.name .. '<br>' .. tpl_args.base_item
else
line_type = 'single'
name_line = tpl_args.name
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_searing_exarch_item or tpl_args.is_eater_of_worlds_item then
symbols = {
tpl_args.is_searing_exarch_item and 'searingexarch' or 'eaterofworlds',
tpl_args.is_eater_of_worlds_item and 'eaterofworlds' or 'searingexarch'
}
elseif tpl_args.is_veiled then
symbols = {'veiled', 'veiled'}
end
infobox
:tag('span')
:addClass('header -' .. line_type)
:tag('span')
:addClass('symbol')
:addClass(symbols and '-' .. symbols[1] or nil)
:done()
:wikitext(name_line)
:tag('span')
:addClass('symbol')
:addClass(symbols and '-' .. symbols[2] or nil)
:done()
:done()
h.add_to_infobox_from_map(tpl_args, infobox, c.item_infobox_groups)
end
if tpl_args.skill_icon ~= nil then
infobox:wikitext(string.format('[[%s]]', tpl_args.skill_icon))
end
return infobox
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)
return tpl_args[key]
end
end
function h.factory.descriptor_value(args)
-- Arguments:
-- key
-- tbl
args = args or {}
return function (tpl_args, 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 = '_range_minimum',
max = '_range_maximum',
avg = '_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
{
-- Item type (weapons, hideout decorations, abyss jewels)
{
show = function (tpl_args)
return i18n.item_class_map[tpl_args.class_id] ~= nil
end,
func = function (tpl_args)
return i18n.item_class_map[tpl_args.class_id]
end,
},
-- Cosmetic item type
{
show = h.factory.maybe_show_infobox_line{
keys = {'cosmetic_type'},
conditions = {processed = true},
},
func = h.factory.display_raw_value('cosmetic_type'),
},
-- Gems
{
show = h.factory.maybe_show_infobox_line{
keys = {'gem_tags'},
conditions = {processed = true},
},
func = function (tpl_args)
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 = {'gem_tier'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'gem_tier',
hide_default = 0,
fmt = '%i',
},
},
fmt = i18n.tooltips.tier,
},
},
{
show = function (tpl_args)
return tpl_args.skill_levels and cfg.class_groups.gems.keys[tpl_args.class_id]
end,
func = function (tpl_args)
local value = {
base = 1,
min = 1,
max = tpl_args.max_level,
}
local options = {
fmt = '%i',
color = 'value',
}
return string.format(
i18n.tooltips.level,
m_util.html.format_value(tpl_args, value, options)
)
end,
},
{
show = function (tpl_args)
return tpl_args.skill_levels and cfg.class_groups.gems.keys[tpl_args.class_id]
end,
func = function (tpl_args)
local parts = {}
for k, v in pairs(m_game.constants.skill.cost_types) do
parts[#parts+1] = {
key = 'cost_' .. k,
func = function (tpl_args, value)
if string.find(k, 'PerMinute', 1, true) then
value = value / 60 -- Cost per second
end
return value
end,
fmt = '%i',
inline = string.format(
'%%s%s %s',
string.find(k, 'Percent', 1, true) and '%%' or '',
v.long_upper
),
}
end
return core.factory.infobox_line{
type = 'gem',
parts = parts,
sep = ', ',
fmt = i18n.tooltips.cost,
}(tpl_args)
end,
},
{
show = function (tpl_args)
return tpl_args.skill_levels and cfg.class_groups.gems.keys[tpl_args.class_id]
end,
func = core.factory.infobox_line{
type = 'gem',
parts = {
{
key = 'mana_reservation_flat',
fmt = '%i',
inline = '%s ' .. m_game.constants.skill.cost_types['Mana'].long_upper,
},
{
key = 'mana_reservation_percent',
fmt = '%i',
inline = '%s%% ' .. m_game.constants.skill.cost_types['Mana'].long_upper,
},
{
key = 'life_reservation_flat',
fmt = '%i',
inline = '%s ' .. m_game.constants.skill.cost_types['Life'].long_upper,
},
{
key = 'life_reservation_percent',
fmt = '%i',
inline = '%s%% ' .. m_game.constants.skill.cost_types['Life'].long_upper,
},
{
key = 'spirit_reservation_flat',
fmt = '%i',
inline = '%s ' .. m_game.constants.skill.cost_types['Spirit'].long_upper,
},
},
sep = ', ',
fmt = i18n.tooltips.reservation,
},
},
{
show = true, -- TODO: Show only if has cost_multiplier
func = core.factory.infobox_line{
type = 'gem',
parts = {
{
key = 'cost_multiplier',
hide_default = 100,
fmt = '%i',
inline = '%s%%',
},
},
fmt = i18n.tooltips.cost_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)
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)
return core.factory.infobox_line{
parts = {
{
key = 'cast_time',
fmt = function (tpl_args, 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)
end,
},
{
show = true, -- TODO: Show only if has attack_time
func = core.factory.infobox_line{
type = 'gem',
parts = {
{
key = 'attack_time',
hide_default = 0,
fmt = '%.2f ' .. m_game.units.seconds.short_lower,
},
},
fmt = i18n.tooltips.attack_time,
},
},
{
show = true, -- TODO: Show only if has critical_strike_chance
func = core.factory.infobox_line{
type = 'gem',
parts = {
{
key = 'critical_strike_chance',
hide_default = 0,
fmt = '%.2f%%',
},
},
fmt = i18n.tooltips.critical_strike_chance,
},
},
{
show = true, -- TODO: Show only if has attack_speed_multiplier
func = core.factory.infobox_line{
type = 'gem',
parts = {
{
key = 'attack_speed_multiplier',
hide_default = 100,
fmt = '%i',
inline = '%s%% ' .. i18n.tooltips.of_base_stat,
},
},
fmt = i18n.tooltips.attack_speed_multiplier,
},
},
{
show = true, -- TODO: Show only if has damage_multiplier
func = core.factory.infobox_line{
type = 'gem',
parts = {
{
key = 'damage_multiplier',
hide_default = 100,
fmt = '%i',
inline = '%s%% ' .. i18n.tooltips.of_base_stat,
},
},
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',
inline = '%s%%',
},
},
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.aoe_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,
},
},
-- Weapons
{
show = h.factory.maybe_show_infobox_line{
keys = {'physical_damage_html'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'physical_damage_html',
color = false, -- html already has color
},
},
fmt = i18n.tooltips.physical_damage,
},
},
{
show = true, -- Elemental Damage
func = function (tpl_args)
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',
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',
color = false, -- html already has color
},
},
fmt = i18n.tooltips.critical_strike_chance,
},
},
{
show = function (tpl_args)
-- Do not show for caster weapons
if tpl_args.tags and (m_util.table.contains(tpl_args.tags, 'staff')
or m_util.table.contains(tpl_args.tags, 'wand')
or m_util.table.contains(tpl_args.tags, 'sceptre')
or m_util.table.contains(tpl_args.tags, 'trap')) then
return false
end
return h.factory.maybe_show_infobox_line{
keys = {'attack_speed_html'},
}(tpl_args)
end,
func = core.factory.infobox_line{
parts = {
{
key = 'attack_speed_html',
color = false, -- html already has color
},
},
fmt = i18n.tooltips.attacks_per_second,
},
},
{
show = function (tpl_args)
-- Do not show for ranged weapons
if tpl_args.tags and m_util.table.contains(tpl_args.tags, 'ranged') then
return false
end
return h.factory.maybe_show_infobox_line{
keys = {'weapon_range_html'},
}(tpl_args)
end,
func = core.factory.infobox_line{
parts = {
{
key = 'weapon_range_html',
color = false, -- html already has color
inline = '%s ' .. m_game.units.metres.long_lower,
},
},
fmt = i18n.tooltips.weapon_range,
},
},
{
show = h.factory.maybe_show_infobox_line{
keys = {'spirit_html'},
},
func = core.factory.infobox_line{
parts = {
{
key = 'spirit_html',
color = false, -- html already has color
hide_default = 0,
hide_default_key = 'spirit',
},
},
fmt = i18n.tooltips.spirit,
},
},
-- Maps
{
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)
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 = i18n.tooltips.map_guild_character,
},
},
{
show = function (tpl_args)
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 = i18n.tooltips.map_guild_character,
},
},
{
show = function (tpl_args)
return tpl_args.class_id == 'Map'
end,
func = core.factory.infobox_line{
type = 'stat',
parts = {
{
key = 'map_item_drop_quantity_+%',
fmt = '%i',
color = 'mod',
inline = '+%s%%',
hide_default = 0,
},
},
fmt = i18n.tooltips.item_quantity,
},
},
{
show = function (tpl_args)
return tpl_args.class_id == 'Map'
end,
func = core.factory.infobox_line{
type = 'stat',
parts = {
{
key = 'map_item_drop_rarity_+%',
fmt = '%i',
color = 'mod',
inline = '+%s%%',
hide_default = 0,
},
},
fmt = i18n.tooltips.item_rarity,
},
},
{
show = function (tpl_args)
return tpl_args.class_id == 'Map'
end,
func = core.factory.infobox_line{
type = 'stat',
parts = {
{
key = 'map_pack_size_+%',
fmt = '%i',
color = 'mod',
inline = '+%s%%',
hide_default = 0,
},
},
fmt = i18n.tooltips.monster_pack_size,
},
},
-- Jewels
{
show = h.factory.maybe_show_infobox_line{
keys = {'jewel_limit'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'jewel_limit',
},
},
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',
color = false, -- html already has color
},
},
fmt = i18n.tooltips.radius,
},
},
-- Flasks
{
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',
color = false, -- html already has color
},
{
key = 'flask_duration_html',
color = false, -- html already has color
},
},
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',
color = false, -- html already has color
},
{
key = 'flask_duration_html',
color = false, -- html already has color
},
},
fmt = i18n.tooltips.flask_life_recovery,
},
},
{
-- don't display for mana/life flasks
show = function (tpl_args)
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',
color = false, -- html already has color
},
},
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',
color = false, -- html already has color
},
{
key = 'charges_max_html',
color = false, -- html already has color
},
},
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
{
show = h.factory.maybe_show_infobox_line{
keys = {'block_html'},
},
func = core.factory.infobox_line{
parts = {
{
key = 'block_html',
color = false, -- html already has color
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',
color = false, -- html already has color
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',
color = false, -- html already has color
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',
color = false, -- html already has color
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',
color = false, -- html already has color
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 = '%i%%',
hide_default = 0,
},
},
fmt = i18n.tooltips.movement_speed,
},
},
-- Stackables
{
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,
},
},
-- Map fragments
{
show = function (tpl_args)
-- Only show for scarabs
if not tpl_args._flags.is_scarab then
return false
end
return h.factory.maybe_show_infobox_line{
keys = {'map_fragment_limit'},
conditions = {processed = true},
}(tpl_args)
end,
func = core.factory.infobox_line{
parts = {
{
key = 'map_fragment_limit',
fmt = '%i',
},
},
fmt = i18n.tooltips.limit,
},
},
-- Essences
{
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,
},
},
-- Oils
{
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,
},
},
-- Tattoos
{
show = h.factory.maybe_show_infobox_line{
keys = {'tattoo_target'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'tattoo_target',
},
},
fmt = i18n.tooltips.tattoo_target,
},
},
{
show = h.factory.maybe_show_infobox_line{
keys = {'tattoo_limit'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'tattoo_limit',
},
},
fmt = i18n.tooltips.tattoo_limit,
},
},
-- 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)
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',
color = false, -- html already has color
},
},
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 = 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 = i18n.tooltips.heist_required_npc,
},
},
-- Sentinels
{
show = h.factory.maybe_show_infobox_line{
keys = {'sentinel_duration'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'sentinel_duration',
fmt = '%i',
},
},
fmt = i18n.tooltips.sentinel_duration,
},
},
{
show = h.factory.maybe_show_infobox_line{
keys = {'sentinel_empowers'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'sentinel_empowers',
fmt = '%i',
},
},
fmt = i18n.tooltips.sentinel_empowers,
},
},
{
show = h.factory.maybe_show_infobox_line{
keys = {'sentinel_empowerment'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'sentinel_empowerment',
fmt = '%i',
},
},
fmt = i18n.tooltips.sentinel_empowerment,
},
},
{
show = h.factory.maybe_show_infobox_line{
keys = {'sentinel_monster', 'sentinel_monster_level'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'sentinel_monster',
},
{
key = 'sentinel_monster_level',
fmt = '%i',
},
},
fmt = i18n.tooltips.sentinel_can_only_empower,
},
},
{
show = h.factory.maybe_show_infobox_line{
keys = {'sentinel_charge'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'sentinel_charge',
fmt = '%i',
},
},
fmt = i18n.tooltips.sentinel_charge,
},
},
-- Corpse items
{
show = h.factory.maybe_show_infobox_line{
keys = {'monster_category_html'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'monster_category_html',
color = false, -- html already has color
},
},
fmt = i18n.tooltips.monster_category,
},
},
-- Embers of the Allflame
{
show = h.factory.maybe_show_infobox_line{
keys = {'pack_size_html'},
},
func = core.factory.infobox_line{
parts = {
{
key = 'pack_size_html',
color = false, -- html already has color
},
},
fmt = i18n.tooltips.pack_size,
},
},
{
show = h.factory.maybe_show_infobox_line{
keys = {'pack_leader_chance'},
conditions = {processed = true},
},
func = function (tpl_args)
return core.factory.infobox_line{
parts = {
{
key = 'pack_leader_chance',
hide_default = 0,
fmt = '%i%%',
},
},
fmt = tpl_args['pack_leader_chance'] < 100 and i18n.tooltips.pack_chance_to_contain_pack_leader or i18n.tooltips.pack_contains_pack_leader,
}(tpl_args)
end,
},
},
-- Requirements
{
-- Talismans display their tier right above requirements
{
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,
},
},
-- Equipment requirements
{
show = true, -- Requires...
func = function (tpl_args)
local parts = {
{
key = 'required_level_final_html',
color = false, -- html already has color
inline = i18n.tooltips.level_inline,
hide_default = 1,
hide_default_key = 'required_level_final',
},
}
for _, attr in ipairs(m_game.constants.attribute_order) do
parts[#parts+1] = {
key = string.format('required_%s_html', attr),
color = false, -- html already has color
inline = '%s ' .. m_game.constants.attributes[attr].short_upper,
hide = function (tpl_args, value)
local min = m_game.constants.characters.minimum_attributes[m_game.constants.attributes[attr].short_lower]
return value.min <= min and value.max <= min
end,
hide_key = string.format('required_%s', attr),
}
end
return core.factory.infobox_line{
parts = parts,
sep = ', ',
fmt = i18n.tooltips.requires,
}(tpl_args)
end,
},
-- Tattoo requirements
{
show = h.factory.maybe_show_infobox_line{
keys = {'tattoo_min_adjacent'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'tattoo_min_adjacent',
fmt = '%i',
hide_default = 0,
},
},
fmt = i18n.tooltips.tattoo_min_adjacent,
},
},
{
show = h.factory.maybe_show_infobox_line{
keys = {'tattoo_max_adjacent'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'tattoo_max_adjacent',
fmt = '%i',
color = 'value',
inline = i18n.tooltips.tattoo_maximum .. ' %s',
hide_default = 0,
},
},
fmt = i18n.tooltips.tattoo_max_adjacent,
},
},
-- Heist gear requirements
{
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',
},
{
key = 'heist_required_job',
},
},
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 stats
{
class = 'tc -mod',
{
show = function (tpl_args)
return cfg.class_groups.gems.keys[tpl_args.class_id] and tpl_args.stat_text
end,
func = h.factory.display_raw_value('stat_text'),
},
{
-- Gem quality selector widget
-- Show in full infobox if gem has alternate quality
-- Never show in hover infobox
show = function (tpl_args)
return cfg.class_groups.gems.keys[tpl_args.class_id] and tpl_args.quality_type1_stat_text and tpl_args._flags.is_alt_quality_gem and tpl_args._infobox_container == 'infobox'
end,
func = function (tpl_args)
local html = mw.html.create()
:tag('br'):done()
local widget = html:tag('span')
:addClass('gemqual-widget')
:wikitext(m_util.html.poe_color('value', string.format(i18n.tooltips.gem_quality_effect_bonus, '')))
local qtypes = {}
for k, quality_data in ipairs(tpl_args.skill_quality) do
qtypes[#qtypes+1] = m_game.constants.item.gem_quality_types[k].long_upper
local stats = widget:tag('span')
:addClass('gemqual-widget__stats')
:attr('data-qid', k)
:wikitext(quality_data.stat_text)
if k == 1 then
stats:addClass('is-selected')
end
end
widget:attr('data-qtypes', table.concat(qtypes, ','))
return tostring(html)
end,
},
{
-- Superior quality bonus
-- Show in full infobox if gem does not have alternate quality
-- Always show in hover infobox
show = function (tpl_args)
return cfg.class_groups.gems.keys[tpl_args.class_id] and tpl_args.quality_type1_stat_text and (not tpl_args._flags.is_alt_quality_gem or tpl_args._infobox_container == 'inline')
end,
func = core.factory.infobox_line{
parts = {
{
key = 'quality_type1_stat_text',
color = 'mod',
inline = i18n.tooltips.gem_quality_effect_bonus,
inline_color = 'value',
},
},
fmt = ' <br> %s',
},
},
},
-- Implicit stats
{
class = 'tc -mod',
{
show = h.factory.maybe_show_infobox_line{
keys = {'implicit_stat_text'},
},
func = function (tpl_args)
if tpl_args._infobox_container == 'inline' then
return h.strip_random_stats(tpl_args, tpl_args.implicit_stat_text)
end
return tpl_args.implicit_stat_text
end,
},
},
-- Explicit stats
{
class = 'tc -mod',
{
show = h.factory.maybe_show_infobox_line{
keys = {'explicit_stat_text'},
},
func = function (tpl_args)
if tpl_args._infobox_container == 'inline' then
return h.strip_random_stats(tpl_args, tpl_args.explicit_stat_text)
end
return tpl_args.explicit_stat_text
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)
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)
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)
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',
},
{
key = 'seed_type_html',
},
{
key = 'seed_required_nearby_seed_tier',
},
},
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
{
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 = 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,
},
},]]
{
show = h.factory.maybe_show_infobox_line{
keys = {'monster_abilities'},
conditions = {processed = true},
},
func = h.factory.display_raw_value('monster_abilities'),
},
},
-- Variations (for hideout decorations)
{
class = 'tc -mod',
{
show = h.factory.maybe_show_infobox_line{
keys = {'variation_count'},
conditions = {processed = true},
},
func = function (tpl_args)
if tpl_args.variation_count > 1 then
return string.format('%i %s', tpl_args.variation_count, i18n.tooltips.variations)
end
return nil
end,
},
},
-- Corrupted
{
class = 'tc -corrupted',
{
show = h.factory.maybe_show_infobox_line{
keys = {'is_corrupted'},
conditions = {processed = true},
},
func = function (tpl_args)
if tpl_args.is_corrupted then
return i18n.tooltips.corrupted
end
return nil
end,
},
},
-- Mirrored
{
class = 'tc -mod',
{
show = h.factory.maybe_show_infobox_line{
keys = {'is_mirrored'},
conditions = {processed = true},
},
func = function (tpl_args)
if tpl_args.is_mirrored then
return i18n.tooltips.mirrored
end
return nil
end,
},
},
-- Unmodifiable
{
class = 'tc -mod',
{
show = h.factory.maybe_show_infobox_line{
keys = {'is_unmodifiable'},
conditions = {processed = true},
},
func = function (tpl_args)
if tpl_args.is_unmodifiable then
return i18n.tooltips.unmodifiable
end
return nil
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)
if tpl_args.cannot_be_traded_or_modified then
return i18n.tooltips.cannot_be_traded_or_modified
end
return nil
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'),
},
},
-- Account-bound
{
class = 'tc -mod',
{
show = h.factory.maybe_show_infobox_line{
keys = {'is_account_bound'},
conditions = {processed = true},
},
func = function (tpl_args)
if tpl_args.is_account_bound then
return i18n.tooltips.account_bound
end
return nil
end,
},
},
}
--
-- 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)
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
:addClass('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)
:addClass(field.class)
:wikitext(field.value)
:done()
end
tr:done()
end
return tostring(tbl)
end
},
},
-- Acquisition
{
heading = i18n.tooltips.acquisition,
{
show = function (tpl_args)
if tpl_args.is_in_game == false then
return true
end
return false
end,
func = function (tpl_args)
local span = mw.html.create('span')
span
:addClass('infobox-disabled-drop')
:wikitext(i18n.tooltips.not_in_game)
:done()
return tostring(span)
end,
},
{
show = function (tpl_args)
if tpl_args.drop_enabled == false and tpl_args.is_in_game ~= false then
return true
end
return false
end,
func = function (tpl_args)
local span = mw.html.create('span')
span
:addClass('infobox-disabled-drop')
:wikitext(i18n.tooltips.drop_disabled)
:done()
return tostring(span)
end,
},
{
show = function (tpl_args)
if tpl_args.is_drop_restricted == true and tpl_args.drop_enabled ~= false then
return true
end
return false
end,
func = function (tpl_args)
local span = mw.html.create('span')
span
:addClass('infobox-restricted-drop')
:wikitext(i18n.tooltips.drop_restricted)
:done()
return tostring(span)
end,
},
{
show = function (tpl_args)
-- Do not show for gems (engraved only)
if tpl_args.class_id == 'Active Skill Gem' or tpl_args.class_id == 'Support Skill Gem' or tpl_args.class_id == 'Meta Skill Gem' then
return false
end
return h.factory.maybe_show_infobox_line{
keys = {'drop_level'},
conditions = {processed = true},
}
end,
func = core.factory.infobox_line{
parts = {
{
key = 'drop_level',
fmt = '%i',
},
{
key = 'drop_level_maximum',
fmt = '%i',
hide_default = 100,
},
},
sep = ' / ',
fmt = i18n.tooltips.drop_level,
},
},
{
show = h.factory.maybe_show_infobox_line{
keys = {'acquisition_tags'},
conditions = {processed = true},
},
func = function (tpl_args)
local tags = {}
for _, v in pairs(tpl_args.acquisition_tags) do
if v then
tags[#tags+1] = string.format('[[%s|%s]]', cfg.acquisition_tags[v].page, cfg.acquisition_tags[v].name)
end
end
return table.concat(tags, ' • ')
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 = {'seal_cost'},
conditions = {processed = true},
},
func = core.factory.infobox_line{
parts = {
{
key = 'seal_cost',
fmt = function (tpl_args, 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)
-- 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)
-- local tbl = mw.html.create('table')
-- tbl
-- --:addClass('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)
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',
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',
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',
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',
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',
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',
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',
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',
color = false, -- the html already contains the colour
},
},
fmt = i18n.tooltips.dps,
},
},
},
-- Metadata
{
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 = i18n.tooltips.item_class,
},
},
{
show = h.factory.maybe_show_infobox_line{
keys = {'metadata_id'},
conditions = {processed = true},
},
func = function (tpl_args)
return core.factory.infobox_line{
parts = {
{
key = 'metadata_id',
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)
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)
-- 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, {'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)
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(rdata)
-- TODO: Verify quests and quest ids?
end
rid = rid + 1
until continue == false
end
function s.process_base_item(tpl_args)
-- error(debug_print.table_to_string(tpl_args))
if not tpl_args._flags.is_derived then
if tpl_args.rarity_id ~= 'normal' then
error(i18n.errors.missing_base_item)
end
return
end
m_item_util = m_item_util or require('Module:Item util')
local qargs = {}
qargs.tables = tpl_args._item_config.tables
qargs.join = {}
for _, tbl in ipairs(tpl_args._item_config.tables) do
if tbl ~= 'items' then
qargs.join[#qargs.join+1] = string.format('items._pageID=%s._pageID', tbl)
end
end
qargs.join = #qargs.join > 0 and table.concat(qargs.join, ', ') or nil
qargs.fields = {
'items._pageName',
'items.name',
'items.metadata_id',
'items.class_id',
'items.rarity_id',
'items.inventory_icon',
}
for _, k in ipairs(tpl_args._base_item_args) do
local arg_def = core.map[k]
if arg_def.field ~= nil then
qargs.fields[#qargs.fields+1] = string.format('%s.%s', arg_def.table, arg_def.field)
end
end
qargs.where = string.format('items.class_id = "%s"', tpl_args.class_id)
local result = m_item_util.query_item({
metadata_id = tpl_args.base_item_id,
page = tpl_args.base_item_page,
item_name_exact = tpl_args.base_item,
debug = tpl_args.debug,
}, qargs)
if result.error then
-- error(debug_print.table_to_string(result))
result.error:throw(true)
end
if result['items.rarity_id'] ~= 'normal' then
-- error(debug_print.table_to_string(result))
error(i18n.errors.base_item_wrong_rarity)
end
tpl_args.base_item = result['items.name']
tpl_args.base_item_page = result['items._pageName']
tpl_args.base_item_id = result['items.metadata_id']
h.process_arguments(tpl_args, {'base_item', 'base_item_page', 'base_item_id'})
tpl_args.base_item_icon = result['items.inventory_icon']
-- Copy values
for _, k in ipairs(tpl_args._base_item_args) do
local arg_def = core.map[k]
if arg_def.func_fetch ~= nil then
arg_def.func_fetch(tpl_args)
elseif arg_def.field ~= nil then
local value = result[string.format('%s.%s', arg_def.table, arg_def.field)]
if value == nil then
if tpl_args.debug then
mw.log(string.format(i18n.debug.base_item_field_not_found, arg_def.table, arg_def.field))
end
else
local default = type(arg_def.default) == 'function' and arg_def.default(tpl_args) or arg_def.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] == arg_def.default or (type(tpl_args[k]) == 'table' and #tpl_args[k] == 0 and type(arg_def.default) == 'table' and #arg_def.default == 0) then
tpl_args[k] = value
if arg_def.func ~= nil then
tpl_args[k] = arg_def.func(tpl_args, tpl_args[k])
end
if arg_def.func_copy ~= nil then
arg_def.func_copy(tpl_args, tpl_args[k])
end
elseif tpl_args.debug then
mw.log(string.format(i18n.debug.field_value_mismatch, k, tostring(tpl_args[k])))
end
end
end
end
-- Fetch implicit mods from base item
local mods = m_cargo.query(
{'items' ,'item_mods'},
{'item_mods.id', 'item_mods.is_random', 'item_mods.text'},
{
join = 'items._pageID=item_mods._pageID',
where = string.format(
'items._pageName="%s" AND item_mods.is_implicit=true',
tpl_args.base_item_page
),
}
)
tpl_args._base_implicit_mods = {}
for _, row in ipairs(mods) do
table.insert(tpl_args._base_implicit_mods, {
id = row['item_mods.id'],
stat_text = row['item_mods.text'],
is_implicit = true,
is_random = m_util.cast.boolean(row['item_mods.is_random']),
result = (function() if row['item_mods.id'] == nil then return false end end)()
})
end
end
function s.table_to_string(tbl)
local result = "{"
for k, v in pairs(tbl) do
-- Converte a chave para string
local key = type(k) == "string" and '"'..k..'"' or tostring(k)
-- Converte o valor para string
local value
if type(v) == "table" then
value = table_to_string(v) -- Recursão para tabelas aninhadas
elseif type(v) == "string" then
value = '"'..v..'"'
else
value = tostring(v)
end
result = result.."["..key.."] = "..value..", "
end
-- Remove a vírgula e espaço extras no final, se existirem
if result:sub(-2) == ", " then
result = result:sub(1, -3)
end
return result.."}"
end
function s.process_mods(tpl_args)
tpl_args._defined_implicit_mods = {}
tpl_args._mods = {}
local function parse_mod_arg(tpl_args, type, index)
local prefix = type .. index
local mod_data = {
is_implicit = type == 'implicit',
is_explicit = type == 'explicit',
is_map_fragment_bonus = type == 'map_fragment_bonus',
is_random = false,
}
local mods_table = mod_data.is_implicit and tpl_args._defined_implicit_mods or tpl_args._mods
-- By mod id
local value = tpl_args[prefix]
if value ~= nil then
mod_data.id = value
mod_data.stat_text = tpl_args[prefix .. '_text']
table.insert(mods_table, mod_data)
return true
end
-- By list of possible mod ids
value = tpl_args[prefix .. '_random_list']
if value ~= nil then
value = m_util.cast.table(value)
for _, id in ipairs(value) do
local mod_data_copy = {}
for k, v in pairs(mod_data) do
mod_data_copy[k] = v
end
mod_data_copy.id = id
mod_data_copy.is_random = true
mod_data_copy.stat_text = tpl_args[prefix .. '_text'] or string.format(i18n.tooltips.random_mod, index)
table.insert(mods_table, mod_data_copy)
end
tpl_args._flags.random_mods = true
return true
end
-- By stat text
value = tpl_args[prefix .. '_text']
if value ~= nil then
mod_data.result = false -- Not stored in Cargo table
mod_data.stat_text = value
table.insert(mods_table, mod_data)
tpl_args._flags.text_modifier = true
return true
end
return false
end -- parse_mod_arg
local types = {
'implicit',
'explicit',
'map_fragment_bonus',
}
for _, t in ipairs(types) do
local i = 1
while parse_mod_arg(tpl_args, t, i) do
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 or {}
if #implicit_mods == 0 then
implicit_mods = tpl_args._base_implicit_mods or {}
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
table.insert(mod_ids, mod_data.id)
if not mod_data.is_random then
table.insert(non_random_mod_ids, mod_data.id)
end
end
table.insert(tpl_args._store_data, {
_table = 'item_mods',
id = mod_data.id,
text = mod_data.stat_text,
is_implicit = mod_data.is_implicit,
is_explicit = mod_data.is_explicit,
is_map_fragment_bonus = mod_data.is_map_fragment_bonus,
is_random = mod_data.is_random,
})
end
local results = m_cargo.query(
{'mods'},
{'mods._pageName', 'mods.id', 'mods.required_level', 'mods.stat_text'},
{
where = string.format(
'mods.id IN ("%s")',
table.concat(mod_ids, '","')
)
}
)
if #results < #mod_ids then
local found_mod_ids = m_util.table.column(results, 'mods.id')
local bogus_mod_ids = m_util.table.diff(mod_ids, found_mod_ids)
error(string.format(i18n.errors.mod_not_found, table.concat(bogus_mod_ids, ', ')))
end
for _, row in ipairs(results) do
local mod_data = mods[row['mods.id']]
mod_data.result = row
if mod_data.is_random == false then
-- Modifiers contribute to level requirement
-- Update base level requirement only if this is an implicit
local args = {
'required_level_final',
mod_data.is_implicit and 'required_level' or nil,
}
for _, v in ipairs(args) do
local req = math.floor(tonumber(row['mods.required_level']) * cfg.item_required_level_modifier_contribution)
if req > tpl_args[v] then
tpl_args[v] = 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(
'mods.id IN ("%s") AND mod_stats.id IS NOT NULL',
table.concat(mod_ids, '","')
),
}
)
for _, row in ipairs(results) do
-- Stat data
local mod_data = mods[row['mods.id']]
mod_data.result.stats = mod_data.result.stats or {}
table.insert(mod_data.result.stats, row)
local stat_id = row['mod_stats.id']
local value = {
min = tonumber(row['mod_stats.min']),
max = tonumber(row['mod_stats.max']),
}
local tbl = mod_data.is_random and '_random_stats' or '_stats'
core.add_stat(tpl_args, stat_id, value, {tbl=tbl, mod=mod_data})
end
if tpl_args.is_sellable == true and tpl_args._flags.sell_prices_override ~= true then
if tpl_args._flags.is_derived then -- Only derived items have explicit modifiers
-- 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
end
if tpl_args.is_sellable == true and tpl_args._flags.sell_prices_override ~= true then
if m_util.table.length(tpl_args.sell_prices) > 0 then
-- 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._store_data[#tpl_args._store_data+1] = {
_table = 'item_sell_prices',
amount = amount,
name = name,
}
end
table.sort(tpl_args.sell_price_order)
end
end
end
function s.process_stats(tpl_args)
tpl_args._stats = tpl_args._stats or {}
-- Extra 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_'}) -- Populates tpl_args.extra_stats
for _, stat in ipairs(tpl_args.extra_stats) do
core.add_stat(tpl_args, stat.id, stat.value or stat)
end
-- Handle local item stat math - additions, increases, etc.
for key, stat_def in pairs(core.stat_map) do
local arg = stat_def.arg or key
local value = {}
if type(arg) == 'table' then
value.min = tpl_args[arg.min]
value.max = tpl_args[arg.max]
value.base_min = tpl_args[arg.min]
value.base_max = tpl_args[arg.max]
else
value.min = tpl_args[arg]
value.max = tpl_args[arg]
value.base = tpl_args[arg]
end
if value.min ~= nil and value.max ~= nil then
-- Determine whether stat is overridden; if so, other calculations can be ignored
local overridden = false
if stat_def.stats_override then
for id, v in pairs(stat_def.stats_override) do
if tpl_args._stats[id] then
-- If for some reason there is more than one of the same override stat, apply the final one.
local stat_value = tpl_args._stats[id][#tpl_args._stats[id]]
if stat_value then
if type(v) == 'table' then
value.min = v.min
value.max = v.max
overridden = true
elseif v then -- Use the stat value
value.min = stat_value.min
value.max = stat_value.max
overridden = true
end
end
end
end
end
if not overridden then
local operations = {'add', 'add_distance', 'increased', 'increased_inverse'}
for _, o in ipairs(operations) do
local stat_ids = stat_def['stats_' .. o]
if stat_ids then
local total = {min=0, max=0}
for _, id in ipairs(stat_ids) do
if tpl_args._stats[id] then
for _, s in ipairs(tpl_args._stats[id]) do
total.min = total.min + s.min
total.max = total.max + s.max
end
end
end
h.stat[o](value, total)
end
end
if stat_def.minimum ~= nil then
for _, k in ipairs({'min', 'max'}) do
if value[k] < stat_def.minimum then
value[k] = stat_def.minimum
end
end
end
end
if value.min == nil or value.max == nil then
value = nil
tpl_args[key] = nil
else
value.avg = (value.min + value.max) / 2
for short_key, range_def in pairs(c.range_map) do
tpl_args[stat_def.field .. range_def] = value[short_key]
end
-- process to HTML to use on list pages or other purposes
h.handle_range_args(tpl_args, key, stat_def.field, value, stat_def.html_fmt_options)
end
end
end
-- Transpose stats into cargo data
for _, tbl in ipairs({'_stats', '_random_stats'}) do
if tpl_args[tbl] then
for stat_id, stats in pairs(tpl_args[tbl]) do
for _, stat_data in ipairs(stats) do
table.insert(tpl_args._store_data, {
_table = 'item_stats',
id = stat_id,
min = stat_data.min,
max = stat_data.max,
avg = stat_data.avg,
mod_id = stat_data.mod and stat_data.mod.id or nil
})
end
end
end
end
end
function s.process_weapon_dps(tpl_args)
if tpl_args.tags and (m_util.table.contains(tpl_args.tags, 'staff')
or m_util.table.contains(tpl_args.tags, 'wand')
or m_util.table.contains(tpl_args.tags, 'sceptre')
or m_util.table.contains(tpl_args.tags, 'trap')) then
return false
end
for key, dps_def 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_def in pairs(c.range_map) do
value[short_key] = 0
for _, damage_key in ipairs(dps_def.damage_args) do
value[short_key] = value[short_key] + (tpl_args[string.format('%s_%s%s', damage_key, var_type, range_def)] or 0)
end
end
end
local value = {}
for short_key, range_def in pairs(c.range_map) do
local min_damage = damage.min[short_key]
local max_damage = damage.max[short_key]
local a = string.format('attack_speed%s', range_def)
-- error(range_def)
local atack_spd = tpl_args[a] or 1
-- error(debug_print.table_to_string(tpl_args))
-- error(debug_print.table_to_string(range_def))
local result = (min_damage + max_damage) / 2 * atack_spd
value[short_key] = result
tpl_args[string.format('%s%s', dps_def.field, range_def)] = result
end
if value.avg > 0 then
h.handle_range_args(tpl_args, key, dps_def.field, value, dps_def.html_fmt_options)
end
end
end
function s.process_gem_variants(tpl_args)
if not tpl_args._flags.is_derived and tpl_args.metadata_id == nil then
-- If this is a base item, then it needs a metadata ID in order to find its variants.
return
end
local variants = {}
local title = mw.title.getCurrentTitle()
local current = {
page = title.prefixedText,
name = tpl_args.name,
id = tpl_args.metadata_id,
icon = tpl_args.inventory_icon,
current = true,
}
local results
local tables = {'items', 'skill_gems', 'skill'}
local fields = {
'items._pageName=page',
'items.name=name',
'items.metadata_id=id',
'items.inventory_icon=icon'
}
local qargs = {
join = 'items._pageID=skill_gems._pageID, items._pageID=skill._pageID',
orderBy = 'skill.skill_id ASC',
}
if tpl_args.class_id == 'Active Skill Gem' then
if tpl_args.is_vaal_skill_gem then
variants.vaal = current
-- Query base skill gem
qargs.where = string.format(
[[items.metadata_id IS NOT NULL
AND items.class_id = "Active Skill Gem"
AND skill_gems.vaal_variant_id = "%s"
AND skill_gems.is_vaal_skill_gem = false]],
tpl_args.metadata_id
)
if title.namespace == 0 then
qargs.where = qargs.where .. ' AND items._pageNamespace = 0'
end
results = m_cargo.query(tables, fields, qargs)
if #results > 0 then
variants.base = results[1]
end
else
-- Determine base skill gem
if tpl_args._flags.is_derived then
variants.base = {
page = tpl_args.base_item_page,
name = tpl_args.base_item,
id = tpl_args.base_item_id,
icon = tpl_args.base_item_icon,
}
else
variants.base = current
end
if tpl_args.vaal_variant_id ~= nil then
-- Query Vaal skill gem variant
qargs.where = string.format(
[[items.metadata_id = "%s"
AND items.class_id = "Active Skill Gem"
AND skill_gems.is_vaal_skill_gem = true]],
tpl_args.vaal_variant_id
)
if title.namespace == 0 then
qargs.where = qargs.where .. ' AND items._pageNamespace = 0'
end
results = m_cargo.query(tables, fields, qargs)
if #results > 0 then
variants.vaal = results[1]
end
end
end
if variants.base then
-- Query transfigured skill gem variants
qargs.where = string.format(
[[items.metadata_id IS NULL
AND items.base_item_id = "%s"
AND items.class_id = "Active Skill Gem"]],
variants.base.id
)
if title.namespace == 0 then
qargs.where = qargs.where .. ' AND items._pageNamespace = 0'
end
results = m_cargo.query(tables, fields, qargs)
if #results > 0 then
variants.transfigured = {}
for _, row in ipairs(results) do
row.current = row.page == title.prefixedText
table.insert(variants.transfigured, row)
end
end
end
elseif tpl_args.class_id == 'Support Skill Gem' then
if tpl_args.is_awakened_support_gem then
variants.awakened = current
-- Query base support gem
qargs.where = string.format(
[[items.metadata_id IS NOT NULL
AND items.class_id = "Support Skill Gem"
AND skill_gems.awakened_variant_id = "%s"
AND skill_gems.is_awakened_support_gem = false]],
tpl_args.metadata_id
)
if title.namespace == 0 then
qargs.where = qargs.where .. ' AND items._pageNamespace = 0'
end
results = m_cargo.query(tables, fields, qargs)
if #results > 0 then
variants.base = results[1]
end
else
variants.base = current
if tpl_args.awakened_variant_id ~= nil then
-- Query awakened support gem variant
qargs.where = string.format(
[[items.metadata_id = "%s"
AND items.class_id = "Support Skill Gem"
AND skill_gems.is_awakened_support_gem = true]],
tpl_args.awakened_variant_id
)
if title.namespace == 0 then
qargs.where = qargs.where .. ' AND items._pageNamespace = 0'
end
results = m_cargo.query(tables, fields, qargs)
if #results > 0 then
variants.awakened = results[1]
end
end
end
end
tpl_args._variants = {}
table.insert(tpl_args._variants, variants.base)
if variants.transfigured then
for _, v in ipairs(variants.transfigured) do
table.insert(tpl_args._variants, v)
end
end
table.insert(tpl_args._variants, variants.vaal)
table.insert(tpl_args._variants, variants.awakened)
end
function s.get_categories(tpl_args)
local cats = {}
if tpl_args._flags.is_prophecy then
table.insert(cats, i18n.categories.prophecies)
elseif tpl_args._flags.is_blight_item then
table.insert(cats, i18n.categories.oils)
elseif tpl_args._flags.is_talisman then
table.insert(cats, i18n.categories.talismans)
elseif tpl_args._flags.is_essence then
table.insert(cats, i18n.categories.essences)
elseif tpl_args._flags.is_fossil then
table.insert(cats, i18n.categories.fossils)
elseif tpl_args._flags.is_scarab then
table.insert(cats, i18n.categories.scarabs)
elseif tpl_args._flags.is_tattoo then
table.insert(cats, i18n.categories.tattoos)
elseif tpl_args._flags.is_delirium_orb then
table.insert(cats, i18n.categories.delirium_orbs)
elseif tpl_args._flags.is_catalyst then
table.insert(cats, i18n.categories.catalysts)
elseif tpl_args.class_id == 'Map' and tpl_args.rarity_id == 'normal' then
table.insert(cats, string.format(i18n.categories.maps_by_series, tpl_args.map_series))
elseif tpl_args.class_id == 'Microtransaction' and tpl_args.cosmetic_type and m_game.constants.item.cosmetic_item_types[tpl_args.cosmetic_type].cats then
for _, v in ipairs(m_game.constants.item.cosmetic_item_types[tpl_args.cosmetic_type].cats) do
table.insert(cats, v)
end
elseif m_game.constants.item.classes[tpl_args.class_id].cats then
for _, v in ipairs(m_game.constants.item.classes[tpl_args.class_id].cats) do
if tpl_args.rarity_id == 'unique' then
table.insert(cats, string.format(i18n.categories.unique_affix, v))
else
table.insert(cats, v)
end
end
else
table.insert(cats, tpl_args.class)
end
if tpl_args._flags.is_derived then
table.insert(cats, i18n.categories.derived_items)
else
table.insert(cats, i18n.categories.base_items)
end
for _, tag in ipairs(tpl_args.acquisition_tags) do
if cfg.acquisition_tags[tag] and cfg.acquisition_tags[tag].cat then
table.insert(cats, cfg.acquisition_tags[tag].cat)
end
end
for _, attr in ipairs(m_game.constants.attribute_order) do
if tpl_args[attr .. '_percent'] and tpl_args[attr .. '_percent'] ~= 0 then
table.insert(cats, string.format('%s %s', m_game.constants.attributes[attr].long_upper, tpl_args.class))
end
end
if cfg.class_groups.gems.keys[tpl_args.class_id] then
for _, tag in ipairs(tpl_args.gem_tags) do
table.insert(cats, string.format(i18n.categories.gem_tag_affix, tag))
end
end
if tpl_args._flags.is_derived and #tpl_args._defined_implicit_mods > 0 then
table.insert(cats, i18n.categories.implicit_modifier_override)
end
if #tpl_args.alternate_art_inventory_icons > 0 then
table.insert(cats, i18n.categories.alternate_artwork)
end
if tpl_args.release_version == nil then
table.insert(cats, i18n.categories.missing_release_version)
end
if tpl_args._flags.text_modifier and not tpl_args.suppress_improper_modifiers_category then
table.insert(cats, i18n.categories.improper_modifiers)
end
for _, k in ipairs({'invalid_recipe_parts', 'duplicate_recipes', 'duplicate_query_area_ids', 'sell_prices_override'}) do
if tpl_args._flags[k] then
table.insert(cats, i18n.categories[k])
end
end
if tpl_args.disable_automatic_recipes == true then
table.insert(cats, i18n.categories.automatic_recipes_disabled)
end
if tpl_args._flags.uses_deprecated_parameters == true then
mw.log('Deprecated parameters used:')
mw.logObject(tpl_args._deprecated_args)
table.insert(cats, i18n.categories.deprecated_parameters)
end
if tpl_args._flags.has_deprecated_skill_parameters then
table.insert(cats, i18n.categories.deprecated_skill_parameters)
end
return cats
end
-- ----------------------------------------------------------------------------
-- Main functions
-- ----------------------------------------------------------------------------
local function _main(tpl_args)
local t = os.clock() -- Start time (for logging)
tpl_args._flags = {}
tpl_args._store_data = {}
tpl_args._errors = {}
-- Item configuration
tpl_args._item_config = s.get_item_config(tpl_args)
-- Add table names to core.map and append range fields.
h.append_schema(tpl_args, tpl_args._item_config.tables)
-- Using general purpose function to handle release and removal versions
m_util.args.version(tpl_args)
-- Assume this is meant to be a derived item if any base item parameters are set
tpl_args._flags.is_derived = m_util.table.has_any_key(tpl_args, {'base_item_id', 'base_item_page', 'base_item'})
-- Must validate some argument early. It is required for future things
h.process_arguments(tpl_args, tpl_args._item_config.args)
-- Base item
s.process_base_item(tpl_args)
-- Process arguments that require a particular flag to be set
-- Used for subsets of item classes that have additional fields
for k, v in pairs(cfg.flagged_args) do
if tpl_args._flags[k] then
h.process_arguments(tpl_args, v.args)
end
end
-- Mods
s.process_mods(tpl_args)
-- Stats
s.process_stats(tpl_args)
-- Calculate and handle weapon dps
if cfg.class_groups.weapons.keys[tpl_args.class_id] then
s.process_weapon_dps(tpl_args)
end
-- Skill gems
if cfg.class_groups.gems.keys[tpl_args.class_id] then
h.process_skill_data(tpl_args)
end
-- Late argument processing
h.process_arguments(tpl_args, tpl_args._item_config.late_args)
-- Recipes
h.process_recipes(tpl_args)
-- Quest reward info
s.process_quest_rewards(tpl_args)
-- Gem variants
if cfg.class_groups.gems.keys[tpl_args.class_id] then
s.process_gem_variants(tpl_args)
end
-- ------------------------------------------------------------------------
-- Infobox handling
-- ------------------------------------------------------------------------
--
-- Tabs
--
local tabs
if type(tpl_args._variants) == 'table' and #tpl_args._variants > 1 then
tabs = mw.html.create('div')
:addClass('itembox-tabs')
for _, v in ipairs(tpl_args._variants) do
local tab = tabs:tag('span')
tab:addClass('itembox-tab')
if v.current then
tab:addClass('-selected')
end
if v.icon then
tab:wikitext(string.format('[[%s|34x34px|link=%s|alt=%s]]', v.icon, v.page, v.name))
else
tab:wikitext(m_util.html.wikilink(v.page, v.name))
end
end
end
--
-- Primary infobox
--
tpl_args._infobox_container = 'infobox'
local infobox = h.make_main_infobox(tpl_args)
if tpl_args.inventory_icon ~= nil and tpl_args.class_id ~= 'DivinationCard' then
infobox: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
tpl_args.infobox_html = tostring(infobox)
--
-- Secondary infobox
--
local extra_infobox = mw.html.create('div')
:addClass('item-box -' .. tpl_args.frame_type)
h.add_to_infobox_from_map(tpl_args, extra_infobox, c.extra_display_groups)
tpl_args.metabox_html = tostring(extra_infobox)
--
-- Output
--
local container = mw.html.create('div')
:addClass('infobox-page-container')
if tabs then
container:node(tabs)
end
container
:node(infobox)
:node(extra_infobox)
-- skill_screenshot is set in skill module
if tpl_args.skill_screenshot then
container:wikitext(string.format('<br>[[%s|300px]]', tpl_args.skill_screenshot))
end
local out = tostring(container)
local cats = s.get_categories(tpl_args)
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
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
-- ------------------------------------------------------------------------
-- Store the infobox so it can be accessed with ease on other pages
tpl_args._infobox_container = 'inline'
tpl_args.html = tostring(h.make_main_infobox(tpl_args))
-- Map argument values for cargo storage
for _, table_name in ipairs(tpl_args._item_config.tables) do
tpl_args._store_data[table_name] = {
_table = table_name,
}
end
for k, v in pairs(tpl_args) do
local arg_def = core.map[k]
if arg_def ~= nil then
if arg_def.table ~= nil and arg_def.field ~= nil then
if arg_def.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], arg_def.table, arg_def.field))
end
end
if tpl_args._store_data[arg_def.table] == nil then
error(string.format('Missing data for table "%s", key "%s", \nvalue:\n "%s" \narg_def:\n%s', arg_def.table, k, mw.dumpObject(v), mw.dumpObject(arg_def)))
end
if type(tpl_args._store_data[arg_def.table]) ~= 'table' then
error(string.format('Unexpected format of data for table "%s", key "%s", \nvalue:\n "%s" \narg_def:\n%s', arg_def.table, k, mw.dumpObject(v), mw.dumpObject(arg_def)))
end
tpl_args._store_data[arg_def.table][arg_def.field] = v
elseif arg_def.table ~= nil and arg_def.field == nil then
error(string.format('Missing field for table "%s", key "%s", \nvalue:\n "%s" \narg_def:\n%s', arg_def.table, k, mw.dumpObject(v), mw.dumpObject(arg_def)))
elseif arg_def.table == nil and arg_def.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._store_data) 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
-- One of the purposes of attaching is to facilitate table recreation.
-- Unfortunately, the Cargo extension's table recreation tool is
-- very slow and often fails to rebuild the entire table.
mw.getCurrentFrame():expandTemplate{
title = string.format(i18n.templates.cargo_attach, data._table),
args = {}
}
break
end
end
end
m_cargo.store(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
-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------
local p = {}
--
-- Template:Item
--
p.main = m_util.misc.invoker_factory(_main, {
parentFirst = true,
})
p.item = p.main
return p