Module:Item/core: Difference between revisions
Jump to navigation
Jump to search
(Infobox rework for skill cost and reservation changes in version 3.14.0) |
(Removed unused code) |
||
Line 139: | Line 139: | ||
hide_default: hide the value if this is set | hide_default: hide the value if this is set | ||
hide_default_key: key to use if it isn't equal to the key parameter | hide_default_key: key to use if it isn't equal to the key parameter | ||
-- from m_util.html.format_value -- | -- from m_util.html.format_value -- | ||
func: Function to transform the value retrieved from the database | func: Function to transform the value retrieved from the database | ||
Line 286: | Line 285: | ||
options.color = 'value' | options.color = 'value' | ||
end | end | ||
parts[#parts+1] = m_util.html.format_value(tpl_args, frame, value, options) | parts[#parts+1] = m_util.html.format_value(tpl_args, frame, value, options) | ||
end | end |
Revision as of 23:57, 6 July 2021
This submodule contains core configuration and functions for use in Module:Item and its other submodules.
The above documentation is transcluded from Module:Item/core/doc.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
-------------------------------------------------------------------------------
--
-- Core confirguation and functions for Module:Item2 and submodules
--
-------------------------------------------------------------------------------
local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')
local m_game = mw.loadData('Module:Game')
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox()
-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Item2/config/sandbox') or mw.loadData('Module:Item2/config')
local i18n = cfg.i18n
-- ----------------------------------------------------------------------------
-- Helper functions
-- ----------------------------------------------------------------------------
local h = {}
function h.process_mod_stats(tpl_args, args)
local lines = {}
local skip = cfg.class_specifics[tpl_args.class_id]
if skip then
skip = skip.skip_stat_lines
end
local random_mods = {}
for _, modinfo in ipairs(tpl_args._mods) do
if modinfo.is_implicit == args.is_implicit then
if modinfo.is_random == true then
if random_mods[modinfo.stat_text] then
table.insert(random_mods[modinfo.stat_text], modinfo)
else
random_mods[modinfo.stat_text] = {modinfo}
end
else
if modinfo.id == nil then
table.insert(lines, modinfo.result)
-- Allows the override of the SMW fetched mod texts for this modifier via <modtype><id>_text parameter
elseif modinfo.text ~= nil then
table.insert(lines, modinfo.text)
else
for _, line in ipairs(m_util.string.split(modinfo.result['mods.stat_text'] or '', '<br>')) do
if line ~= '' then
if skip == nil then
table.insert(lines, line)
else
local skipped = false
for _, pattern in ipairs(skip) do
if string.match(line, pattern) then
skipped = true
break
end
end
if not skipped then
table.insert(lines, line)
end
end
end
end
end
end
end
end
for stat_text, modinfo_list in pairs(random_mods) do
local text = {}
for _, modinfo in ipairs(modinfo_list) do
table.insert(text, modinfo.result['mods.stat_text'])
end
local tbl = mw.html.create('table')
tbl
:attr('class', 'random-modifier-stats mw-collapsed')
:attr('style', 'text-align: left')
:tag('tr')
:tag('th')
:attr('class', 'mw-customtoggle-31')
:wikitext(stat_text)
:done()
:done()
:tag('tr')
:attr('class', 'mw-collapsible mw-collapsed')
:attr('id', 'mw-customcollapsible-31')
:tag('td')
:wikitext(table.concat(text, '<hr style="width: 20%">'))
:done()
:done()
table.insert(lines, tostring(tbl))
end
if #lines == 0 then
return
else
return table.concat(lines, '<br>')
end
end
--
-- Factory
--
h.factory = {}
function h.factory.cast_text(k, args)
args = args or {}
return function (tpl_args, frame)
tpl_args[args.key_out or k] = m_util.cast.text(tpl_args[k])
end
end
-- ----------------------------------------------------------------------------
-- Core
-- ----------------------------------------------------------------------------
local core = {}
core.factory = {}
function core.factory.infobox_line(args)
--[[
args:
type: How to read data from tpl_args using the given keys. nil = Regular, gem = Skill progression, stat = Stats
parts:
[n]:
key: key to use. If type = gem and table is given, parse for subfield along path
allow_zero: allow zero values
hide: Hide part if this function returns true
hide_key: Alternate key to use to retrieve the value
hide_default: hide the value if this is set
hide_default_key: key to use if it isn't equal to the key parameter
-- from m_util.html.format_value --
func: Function to transform the value retrieved from the database
fmt: Format string (or function that returns format string) to use for the value. Default: '%s'
fmt_range: Format string to use for the value range. Default: '(%s-%s)'
color: poe_color code to use for the value range. False for no color. Default: 'mod'
class: Additional css class added to color tag
inline: Format string to use for the output
inline_color: poe_color code to use for the output. False for no color. Default: 'default'
inline_class: Additional css class added to inline color tag
sep: If specified, parts are joined with this separator before being formatted for output
fmt: Format string to use for output. If not specified, parts are simply concatenated
color: poe_color code to use for output. Default: no color
class: Additional css class added to output
--]]
args.parts = args.parts or {}
return function (tpl_args, frame)
local base_values = {}
local temp_values = {}
if args.type == 'gem' then
-- Skill progression. Look for keys in tpl_args.skill_levels
if not cfg.class_groups.gems.keys[tpl_args.class_id] then
-- Skip if this item is not actually a gem
return
end
for i, data in ipairs(args.parts) do
if data.key then
local path = type(data.key) == 'table' and data.key or {data.key}
-- Check for static value
local value = tpl_args.skill_levels[0]
for _, p in ipairs(path) do -- Parse for subfield along path
if value[p] == nil then
value = nil
break
else
value = value[p]
end
end
if value ~= nil then
base_values[i] = value
temp_values[#temp_values+1] = {value={min=value,max=value}, index=i}
else -- Check for leveled values
value = {
min = tpl_args.skill_levels[1],
max = tpl_args.skill_levels[tpl_args.max_level],
}
for k, _ in pairs(value) do
for _, p in ipairs(path) do -- Parse for subfield along path
if value[k][p] == nil then
value[k] = nil
break
else
value[k] = value[k][p]
end
end
end
if value.min ~= nil and value.max ~= nil then
base_values[i] = value.min
temp_values[#temp_values+1] = {value=value, index=i}
end
end
end
end
elseif args.type == 'stat' then
-- Stats. Look for key in tpl_args._stats
for i, data in ipairs(args.parts) do
local value = tpl_args._stats[data.key]
if value ~= nil then
base_values[i] = value.min
temp_values[#temp_values+1] = {value=value, index=i}
end
end
else
-- Regular. Look for key exactly as written in tpl_args
for i, data in ipairs(args.parts) do
base_values[i] = tpl_args[data.key]
local value = {}
if tpl_args[data.key .. '_range_minimum'] ~= nil then
value.min = tpl_args[data.key .. '_range_minimum']
value.max = tpl_args[data.key .. '_range_maximum']
elseif tpl_args[data.key] ~= nil then
value.min = tpl_args[data.key]
value.max = tpl_args[data.key]
end
if value.min == nil then
else
temp_values[#temp_values+1] = {value=value, index=i}
end
end
end
local final_values = {}
for i, data in ipairs(temp_values) do
local opt = args.parts[data.index]
local hide = false
if type(opt.hide) == 'function' then
local v = data.value
if opt.hide_key then
v = {
min = tpl_args[opt.hide_key .. '_range_minimum'],
max = tpl_args[opt.hide_key .. '_range_maximum'],
}
if v.min == nil or v.max == nil then
v = tpl_args[opt.hide_key]
end
end
hide = opt.hide(tpl_args, frame, v)
elseif opt.hide_default ~= nil then
if opt.hide_default_key then
local v = {
min = tpl_args[opt.hide_default_key .. '_range_minimum'],
max = tpl_args[opt.hide_default_key .. '_range_maximum'],
}
if v.min == nil or v.max == nil then
if opt.hide_default == tpl_args[opt.hide_default_key] then
hide = true
end
elseif opt.hide_default == v.min or opt.hide_default == v.max then
hide = true
end
else
local v = data.value
if opt.hide_default == v.min or opt.hide_default == v.max then
hide = true
end
end
end
if not hide then
table.insert(final_values, data)
end
end
-- all zeros = dont display and return early
if #final_values == 0 then
return nil
end
local parts = {}
for i, data in ipairs(final_values) do
local value = data.value
value.base = base_values[data.index]
local options = args.parts[data.index]
if args.type == 'gem' and options.color == nil then
-- Display skill progression range values as unmodified (white)
options.color = 'value'
end
parts[#parts+1] = m_util.html.format_value(tpl_args, frame, value, options)
end
if args.sep then
-- Join parts with separator before formatting
parts = {table.concat(parts, args.sep)}
end
-- Build output string
local out
if args.fmt then
out = string.format(args.fmt, unpack(parts))
else
out = table.concat(parts)
end
if args.color then
out = m_util.html.poe_color(args.color, out, args.class)
elseif args.class then
out = tostring(mw.html.create('em')
:attr('class', class)
:wikitext(out)
)
end
return out
end
end
function core.factory.damage_html(args)
return function(tpl_args, frame)
local keys = {
min = args.key .. '_damage_min',
max = args.key .. '_damage_max',
}
local value = {}
for ktype, key in pairs(keys) do
value[ktype] = core.factory.infobox_line{
parts = {
{
key = key,
color = false,
hide_default = 0,
}
}
}(tpl_args, frame)
end
if value.min and value.max then
local color = args.key or false
local range_fmt
if tpl_args[keys.min .. '_range_minimum'] ~= tpl_args[keys.min .. '_range_maximum'] or tpl_args[keys.max .. '_range_minimum'] ~= tpl_args[keys.max .. '_range_maximum'] then
-- Variable damage range, based on modifier rolls
if args.key == 'physical' then
color = 'mod'
end
range_fmt = i18n.fmt.variable_damage_range
else
-- Standard damage range
if args.key == 'physical' then
color = 'value'
end
range_fmt = i18n.fmt.standard_damage_range
end
value = string.format(range_fmt, value.min, value.max)
if color then
value = m_util.html.poe_color(color, value)
end
tpl_args[args.key .. '_damage_html'] = value
end
end
end
function core.stats_update(tpl_args, id, value, modid, key)
if tpl_args[key][id] == nil then
tpl_args[key][id] = {
references = {modid},
min = value.min,
max = value.max,
avg = value.avg,
}
else
if modid ~= nil then
table.insert(tpl_args[key][id].references, modid)
end
tpl_args[key][id].min = tpl_args[key][id].min + value.min
tpl_args[key][id].max = tpl_args[key][id].max + value.max
tpl_args[key][id].avg = tpl_args[key][id].avg + value.avg
end
end
--
-- argument mapping
--
-- format:
-- tpl_args key = {
-- no_copy = true or nil -- When loading an base item, dont copy this key
-- property = 'prop', -- Property associated with this key
-- property_func = function or nil -- Function to unpack the property into a native lua value.
-- If not specified, func is used.
-- If neither is specified, value is copied as string
-- func = function or nil -- Function to unpack the argument into a native lua value and validate it.
-- If not specified, value will not be set.
-- default = object -- Default value if the parameter is nil
-- }
core.map = {
-- special params
html = {
no_copy = true,
field = 'html',
type = 'Text',
func = nil,
},
html_extra = {
no_copy = true,
field = 'html_extra',
type = 'Text',
func = nil,
},
implicit_stat_text = {
field = 'implicit_stat_text',
type = 'Text',
func = function(tpl_args, frame)
tpl_args.implicit_stat_text = h.process_mod_stats(tpl_args, {is_implicit=true})
end,
},
explicit_stat_text = {
field = 'explicit_stat_text',
type = 'Text',
func = function(tpl_args, frame)
tpl_args.explicit_stat_text = h.process_mod_stats(tpl_args, {is_implicit=false})
if tpl_args.is_talisman or tpl_args.is_corrupted then
if tpl_args.explicit_stat_text == nil or tpl_args.explicit_stat_text == '' then
tpl_args.explicit_stat_text = i18n.tooltips.corrupted
else
tpl_args.explicit_stat_text = (tpl_args.explicit_stat_text or '') .. '<br>' .. i18n.tooltips.corrupted
end
end
end,
},
stat_text = {
field = 'stat_text',
type = 'Text',
func = function(tpl_args, frame)
local sep = ''
if tpl_args.implicit_stat_text and tpl_args.explicit_stat_text then
sep = string.format('<span class="item-stat-separator -%s"></span>', tpl_args.frame_type)
end
local text = (tpl_args.implicit_stat_text or '') .. sep .. (tpl_args.explicit_stat_text or '')
if string.len(text) > 0 then
tpl_args.stat_text = text
end
end,
},
class = {
no_copy = true,
field = 'class',
type = 'String',
func = function (tpl_args, frame)
tpl_args.class = m_game.constants.item.classes[tpl_args.class_id]['long_upper']
-- Avoids errors with empty item class names later on
if tpl_args.class == '' then
tpl_args.class = nil
end
end,
},
-- processed in build_item_classes
class_id = {
no_copy = true,
field = 'class_id',
type = 'String',
func = function (tpl_args, frame)
if m_game.constants.item.classes[tpl_args.class_id] == nil then
error(string.format(i18n.errors.invalid_class_id, tostring(tpl_args.class_id)))
end
end
},
-- generic
rarity_id = {
no_copy = true,
field = 'rarity_id',
type = 'String',
func = function (tpl_args, frame)
if m_game.constants.rarities[tpl_args.rarity_id] == nil then
error(string.format(i18n.errors.invalid_rarity_id, tostring(tpl_args.rarity_id)))
end
end
},
rarity = {
no_copy = true,
field = 'rarity',
type = 'String',
func = function(tpl_args, frame)
tpl_args.rarity = m_game.constants.rarities[tpl_args.rarity_id]['long_upper']
end
},
name = {
no_copy = true,
field = 'name',
type = 'String',
func = nil,
},
size_x = {
field = 'size_x',
type = 'Integer',
func = m_util.cast.factory.number('size_x'),
},
size_y = {
field = 'size_y',
type = 'Integer',
func = m_util.cast.factory.number('size_y'),
},
drop_rarities_ids = {
no_copy = true,
field = 'drop_rarity_ids',
type = 'List (,) of Text',
func = function(tpl_args, frame)
tpl_args.drop_rarities_ids = nil
if true then return end
-- Drop rarities only matter for base items.
if tpl_args.rarity_id ~= 'normal' then
return
end
if tpl_args.drop_rarities_ids == nil then
tpl_args.drop_rarities_ids = {}
return
end
tpl_args.drop_rarities_ids = m_util.string.split(tpl_args.drop_rarities_ids, ',%s*')
for _, rarity_id in ipairs(tpl_args.drop_rarities_ids) do
if m_game.constants.rarities[rarity_id] == nil then
error(string.format(i18n.errors.invalid_rarity_id, tostring(rarity_id)))
end
end
end,
},
drop_rarities = {
no_copy = true,
field = nil,
type = 'List (,) of Text',
func = function(tpl_args, frame)
tpl_args.drop_rarities = nil
if true then return end
-- Drop rarities only matter for base items.
if tpl_args.rarity_id ~= 'normal' then
return
end
local rarities = {}
for _, rarity_id in ipairs(tpl_args.drop_rarities_ids) do
rarities[#rarities+1] = m_game.constants.rarities[rarity_id].full
end
tpl_args.drop_rarities = rarities
end,
},
drop_enabled = {
no_copy = true,
field = 'drop_enabled',
type = 'Boolean',
func = m_util.cast.factory.boolean('drop_enabled'),
default = true,
},
drop_level = {
no_copy = true,
field = 'drop_level',
type = 'Integer',
func = m_util.cast.factory.number('drop_level'),
},
drop_level_maximum = {
no_copy = true,
field = 'drop_level_maximum',
type = 'Integer',
func = m_util.cast.factory.number('drop_level_maximum'),
},
drop_leagues = {
no_copy = true,
field = 'drop_leagues',
type = 'List (,) of String',
func = m_util.cast.factory.assoc_table('drop_leagues', {tbl=m_game.constants.leagues, errmsg=i18n.errors.invalid_league}),
},
drop_areas = {
no_copy = true,
field = 'drop_areas',
type = 'List (,) of String',
func = function(tpl_args, frame)
if tpl_args.drop_areas ~= nil then
tpl_args.drop_areas = m_util.string.split(tpl_args.drop_areas, ',%s*')
tpl_args.drop_areas_data = m_cargo.array_query{
tables={'areas'},
fields={'areas._pageName', 'areas.id', 'areas.name', 'areas.main_page'},
id_field='areas.id',
id_array=tpl_args.drop_areas,
query={limit=5000},
}
end
-- find areas based on item tags for atlas bases
local query_data
for _, tag in ipairs(tpl_args.tags or {}) do
query_data = nil
if string.match(tag, '[%w_]*atlas[%w_]*') ~= nil and tag ~= 'atlas_base_type' then
query_data = m_cargo.query(
{'atlas_maps', 'maps', 'areas', 'atlas_base_item_types'},
{'areas._pageName', 'areas.id', 'areas.name', 'areas.main_page'},
{
join='atlas_maps._pageID=maps._pageID, maps.area_id=areas.id, atlas_maps.region_id=atlas_base_item_types.region_id',
where=string.format([[
atlas_base_item_types.tag = "%s"
AND atlas_base_item_types.weight > 0
AND (
atlas_maps.map_tier0 >= tier_min AND atlas_maps.map_tier0 <= atlas_base_item_types.tier_max
OR atlas_maps.map_tier1 >= tier_min AND atlas_maps.map_tier1 <= atlas_base_item_types.tier_max
OR atlas_maps.map_tier2 >= tier_min AND atlas_maps.map_tier2 <= atlas_base_item_types.tier_max
OR atlas_maps.map_tier3 >= tier_min AND atlas_maps.map_tier3 <= atlas_base_item_types.tier_max
OR atlas_maps.map_tier4 >= tier_min AND atlas_maps.map_tier4 <= atlas_base_item_types.tier_max
)]],
tag),
groupBy='areas.id',
}
)
end
if query_data ~= nil then
-- in case no manual drop areas have been set
if tpl_args.drop_areas == nil then
tpl_args.drop_areas = {}
tpl_args.drop_areas_data = {}
end
local drop_areas_assoc = {}
for _, id in ipairs(tpl_args.drop_areas) do
drop_areas_assoc[id] = true
end
local duplicates = {}
for _, row in ipairs(query_data) do
if drop_areas_assoc[row['areas.id']] == nil then
tpl_args.drop_areas[#tpl_args.drop_areas+1] = row['areas.id']
tpl_args.drop_areas_data[#tpl_args.drop_areas_data+1] = row
else
duplicates[#duplicates+1] = row['areas.id']
end
end
if #duplicates > 0 then
tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.duplicate_area_id_from_query, table.concat(duplicates, ', '))
tpl_args._flags.duplicate_query_area_ids = true
end
end
end
end,
},
drop_monsters = {
no_copy = true,
field = 'drop_monsters',
type = 'List (,) of Text',
func = function (tpl_args, frame)
if tpl_args.drop_monsters ~= nil then
tpl_args.drop_monsters = m_util.string.split(tpl_args.drop_monsters, ',%s*')
end
end,
},
drop_text = {
no_copy = true,
field = 'drop_text',
type = 'Text',
func = h.factory.cast_text('drop_text'),
},
required_level = {
field = 'required_level_base',
type = 'Integer',
func = m_util.cast.factory.number('required_level'),
default = 1,
},
required_level_final = {
field = 'required_level',
type = 'Integer',
func = function(tpl_args, frame)
tpl_args.required_level_final = tpl_args.required_level
if tpl_args.required_level_final < cfg.base_item_required_level_threshold then
tpl_args.required_level_final = 1
end
end,
default = 1,
},
required_dexterity = {
field = 'required_dexterity',
type = 'Integer',
func = m_util.cast.factory.number('required_dexterity'),
default = 0,
},
required_strength = {
field = 'required_strength',
type = 'Integer',
func = m_util.cast.factory.number('required_strength'),
default = 0,
},
required_intelligence = {
field = 'required_intelligence',
type = 'Integer',
func = m_util.cast.factory.number('required_intelligence'),
default = 0,
},
inventory_icon = {
no_copy = true,
field = 'inventory_icon',
type = 'String',
func = function(tpl_args, frame)
if not tpl_args.inventory_icon then
-- Certain types of items have default inventory icons
if i18n.default_inventory_icons[tpl_args.class_id] then
tpl_args.inventory_icon = i18n.default_inventory_icons[tpl_args.class_id]
elseif tpl_args.base_item_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy' then
tpl_args.inventory_icon = i18n.default_inventory_icons['Prophecy']
end
end
tpl_args.inventory_icon_id = tpl_args.inventory_icon or tpl_args.name
tpl_args.inventory_icon = string.format(i18n.files.inventory_icon, tpl_args.inventory_icon_id)
end,
},
-- note: this must be called after inventory_icon to work correctly as it depends on tpl_args.inventory_icon_id being set
alternate_art_inventory_icons = {
no_copy = true,
field = 'alternate_art_inventory_icons',
type = 'List (,) of String',
func = function(tpl_args, frame)
local icons = {}
if tpl_args.alternate_art_inventory_icons ~= nil then
local names = m_util.string.split(tpl_args.alternate_art_inventory_icons, ',%s*')
for _, name in ipairs(names) do
icons[#icons+1] = string.format(i18n.files.inventory_icon, string.format('%s %s', tpl_args.inventory_icon_id, name))
end
end
tpl_args.alternate_art_inventory_icons = icons
end,
default = function (tpl_args, frame) return {} end,
},
cannot_be_traded_or_modified = {
no_copy = true,
field = 'cannot_be_traded_or_modified',
type = 'Boolean',
func = m_util.cast.factory.boolean('cannot_be_traded_or_modified'),
default = false,
},
help_text = {
debug_ignore_nil = true,
field = 'help_text',
type = 'Text',
func = h.factory.cast_text('help_text'),
},
flavour_text = {
no_copy = true,
field = 'flavour_text',
type = 'Text',
func = h.factory.cast_text('flavour_text'),
},
flavour_text_id = {
no_copy = true,
field = 'flavour_text_id',
type = 'String',
func = nil,
},
tags = {
field = 'tags',
type = 'List (,) of String',
func = m_util.cast.factory.assoc_table('tags', {
tbl = m_game.constants.tags,
errmsg = i18n.errors.invalid_tag,
}),
},
metadata_id = {
no_copy = true,
field = 'metadata_id',
type = 'String',
--type = 'String(unique; size=200)',
func = function(tpl_args, frame)
if tpl_args.metadata_id == nil then
return
end
local results = m_cargo.query(
{'items'},
{'items._pageName'},
{
where=string.format('items.metadata_id="%s" AND items._pageName != "%s"', tpl_args.metadata_id, mw.title.getCurrentTitle().fullText)
}
)
if #results > 0 and tpl_args.debug_id == nil and not tpl_args.debug then
error(string.format(i18n.errors.duplicate_metadata, tpl_args.metadata_id, results[1]['items._pageName']))
end
end,
},
influences = {
no_copy = true,
field = 'influences',
type = 'List (,) of String',
func = m_util.cast.factory.assoc_table('influences', {
tbl = m_game.constants.influences,
errmsg = i18n.errors.invalid_influence,
}),
},
is_fractured = {
no_copy = true,
field = 'is_fractured',
type = 'Boolean',
func = m_util.cast.factory.boolean('is_fractured'),
default = false,
},
is_synthesised = {
no_copy = true,
field = 'is_synthesised',
type = 'Boolean',
func = m_util.cast.factory.boolean('is_synthesised'),
default = false,
},
is_veiled = {
no_copy = true,
field = 'is_veiled',
type = 'Boolean',
func = m_util.cast.factory.boolean('is_veiled'),
default = false,
},
is_replica = {
no_copy = true,
field = 'is_replica',
type = 'Boolean',
func = function(tpl_args, frame)
m_util.cast.factory.boolean('is_replica')(tpl_args, frame)
if tpl_args.is_replica == true and tpl_args.rarity_id ~= 'unique' then
error(string.format(i18n.errors.non_unique_flag, 'is_replica'))
end
end,
default = false,
},
is_corrupted = {
no_copy = true,
field = 'is_corrupted',
type = 'Boolean',
func = m_util.cast.factory.boolean('is_corrupted'),
default = false,
},
is_relic = {
no_copy = true,
field = 'is_relic',
type = 'Boolean',
func = function(tpl_args, frame)
m_util.cast.factory.boolean('is_relic')(tpl_args, frame)
if tpl_args.is_relic == true and tpl_args.rarity_id ~= 'unique' then
error(string.format(i18n.errors.non_unique_flag, 'is_relic'))
end
end,
default = false,
},
is_fated = {
no_copy = true,
field = 'is_fated',
type = 'Boolean',
func = function(tpl_args, frame)
m_util.cast.factory.boolean('is_fated')(tpl_args, frame)
if tpl_args.is_fated == true and tpl_args.rarity_id ~= 'unique' then
error(string.format(i18n.errors.non_unique_flag, 'is_fated'))
end
end,
default = false,
},
is_prophecy = {
no_copy = true,
field = nil,
type = nil,
func = function(tpl_args, frame)
tpl_args._flags.is_prophecy = (tpl_args.metadata_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy' or tpl_args.base_item == cfg.prophecy_base_item or tpl_args.base_item_page == cfg.prophecy_base_item_page or tpl_args.base_item_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy')
end
},
is_blight_item = {
no_copy = true,
field = nil,
type = nil,
func = function(tpl_args, frame)
tpl_args._flags.is_blight_item = (tpl_args.blight_item_tier ~= nil)
end
},
is_drop_restricted = {
no_copy = true,
field = 'is_drop_restricted',
type = 'Boolean',
func = m_util.cast.factory.boolean('is_drop_restricted'),
default = function(tpl_args, frame)
-- Generally items that are obtained only from specific monsters can't be obtained via cards or other means unless specifically specified
for _, class in ipairs({'Incubator'}) do
if tpl_args.class_id == class then
return true
end
end
for _, var in ipairs({'is_talisman', 'is_essence', 'is_fated', 'is_replica', 'is_relic', 'drop_monsters'}) do
if tpl_args[var] then
return true
end
end
for _, flag in ipairs({'is_prophecy', 'is_blight_item'}) do
if tpl_args._flags[flag] then
return true
end
end
return false
end,
},
purchase_costs = {
func = function(tpl_args, frame)
local purchase_costs = {}
for _, rarity_id in ipairs(m_game.constants.rarity_order) do
local rtbl = {}
local prefix = string.format('purchase_cost_%s', rarity_id)
local i = 1
while i ~= -1 do
local iprefix = prefix .. i
local values = {
name = tpl_args[iprefix .. '_name'],
amount = tonumber(tpl_args[iprefix .. '_amount']),
rarity = rarity_id,
}
if values.name ~= nil and values.amount ~= nil then
rtbl[#rtbl+1] = values
i = i + 1
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_purchase_costs',
amount = values.amount,
name = values.name,
rarity = values.rarity,
}
else
i = -1
end
end
purchase_costs[rarity_id] = rtbl
end
tpl_args.purchase_costs = purchase_costs
end,
func_fetch = function(tpl_args, frame)
if tpl_args.rarity_id ~= 'unique' then
return
end
local results = m_cargo.query(
{'items' ,'item_purchase_costs'},
{'item_purchase_costs.amount', 'item_purchase_costs.name', 'item_purchase_costs.rarity'},
{
join = 'items._pageID=item_purchase_costs._pageID',
where = string.format('items._pageName="%s" AND item_purchase_costs.rarity="unique"', tpl_args.base_item_page),
}
)
for _, row in ipairs(results) do
local values = {
rarity = row['item_purchase_costs.rarity'],
name = row['item_purchase_costs.name'],
amount = tonumber(row['item_purchase_costs.amount']),
}
local datavar = tpl_args.purchase_costs[string.lower(values.rarity)]
datavar[#datavar+1] = values
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_purchase_costs',
amount = values.amount,
name = values.name,
rarity = values.rarity,
}
end
end,
},
sell_prices_override = {
no_copy = true,
func = function(tpl_args, frame)
-- these variables are also used by mods when setting automatic sell prices
tpl_args.sell_prices = {}
tpl_args.sell_price_order = {}
local name
local amount
local i = 0
repeat
i = i + 1
name = tpl_args[string.format('sell_price%s_name', i)]
amount = tpl_args[string.format('sell_price%s_amount', i)]
if name ~= nil and amount ~= nil then
tpl_args.sell_price_order[#tpl_args.sell_price_order+1] = name
tpl_args.sell_prices[name] = amount
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_sell_prices',
amount = amount,
name = name,
}
end
until name == nil or amount == nil
-- if sell prices are set, the override is active
for _, _ in pairs(tpl_args.sell_prices) do
tpl_args._flags.sell_prices_override = true
break
end
end,
},
--
-- specific section
--
-- Most item classes
quality = {
no_copy = true,
field = 'quality',
type = 'Integer',
-- Can be set manually, but default to Q20 for unique weapons/body armours
-- Also must copy to stat for the stat adjustments to work properly
func = function(tpl_args, frame)
local quality = tonumber(tpl_args.quality)
--
if quality == nil then
if tpl_args.rarity_id ~= 'unique' then
quality = 0
elseif cfg.class_groups.weapons.keys[tpl_args.class_id] or cfg.class_groups.armor.keys[tpl_args.class_id] then
quality = 20
else
quality = 0
end
end
tpl_args.quality = quality
local stat = {
min = quality,
max = quality,
avg = quality,
}
core.stats_update(tpl_args, 'quality', stat, nil, '_stats')
if tpl_args.class_id == 'UtilityFlask' or tpl_args.class_id == 'UtilityFlaskCritical' then
core.stats_update(tpl_args, 'quality_flask_duration', stat, nil, '_stats')
-- quality is added to quantity for maps
elseif tpl_args.class_id == 'Map' then
core.stats_update(tpl_args, 'map_item_drop_quantity_+%', stat, nil, '_stats')
end
end,
},
-- amulets
is_talisman = {
field = 'is_talisman',
type = 'Boolean',
func = m_util.cast.factory.boolean('is_talisman'),
default = false,
},
talisman_tier = {
field = 'talisman_tier',
type = 'Integer',
func = m_util.cast.factory.number('talisman_tier'),
},
-- flasks
charges_max = {
field = 'charges_max',
type = 'Integer',
func = m_util.cast.factory.number('charges_max'),
},
charges_per_use = {
field = 'charges_per_use',
type = 'Integer',
func = m_util.cast.factory.number('charges_per_use'),
},
flask_mana = {
field = 'mana',
type = 'Integer',
func = m_util.cast.factory.number('flask_mana'),
},
flask_life = {
field = 'life',
type = 'Integer',
func = m_util.cast.factory.number('flask_life'),
},
flask_duration = {
field = 'duration',
type = 'Float',
func = m_util.cast.factory.number('flask_duration'),
},
buff_id = {
field = 'id',
type = 'String',
func = nil,
},
buff_values = {
field = 'buff_values',
type = 'List (,) of Integer',
func = function(tpl_args, frame)
local values = {}
local i = 0
repeat
i = i + 1
local key = 'buff_value' .. i
values[i] = tonumber(tpl_args[key])
tpl_args[key] = nil
until values[i] == nil
-- needed so the values copyied from unique item base isn't overriden
if #values >= 1 then
tpl_args.buff_values = values
end
end,
func_copy = function(tpl_args, frame)
tpl_args.buff_values = m_util.string.split(tpl_args.buff_values, ',%s*')
end,
default = function (tpl_args, frame) return {} end,
},
buff_stat_text = {
field = 'stat_text',
type = 'String',
func = nil,
},
buff_icon = {
field = 'icon',
type = 'String',
func = function(tpl_args, frame)
tpl_args.buff_icon = string.format(i18n.files.status_icon, tpl_args.name)
end,
},
-- weapons
critical_strike_chance = {
field = 'critical_strike_chance',
type = 'Float',
func = m_util.cast.factory.number('critical_strike_chance'),
},
attack_speed = {
field = 'attack_speed',
type = 'Float',
func = m_util.cast.factory.number('attack_speed'),
},
weapon_range = {
field = 'weapon_range',
type = 'Integer',
func = m_util.cast.factory.number('weapon_range'),
},
physical_damage_min = {
field = 'physical_damage_min',
type = 'Integer',
func = m_util.cast.factory.number('physical_damage_min'),
},
physical_damage_max = {
field = 'physical_damage_max',
type = 'Integer',
func = m_util.cast.factory.number('physical_damage_max'),
},
fire_damage_min = {
field = 'fire_damage_min',
type = 'Integer',
func = m_util.cast.factory.number('fire_damage_min'),
default = 0,
},
fire_damage_max = {
field = 'fire_damage_max',
type = 'Integer',
func = m_util.cast.factory.number('fire_damage_max'),
default = 0,
},
cold_damage_min = {
field = 'cold_damage_min',
type = 'Integer',
func = m_util.cast.factory.number('cold_damage_min'),
default = 0,
},
cold_damage_max = {
field = 'cold_damage_max',
type = 'Integer',
func = m_util.cast.factory.number('cold_damage_max'),
default = 0,
},
lightning_damage_min = {
field = 'lightning_damage_min',
type = 'Integer',
func = m_util.cast.factory.number('lightning_damage_min'),
default = 0,
},
lightning_damage_max = {
field = 'lightning_damage_max',
type = 'Integer',
func = m_util.cast.factory.number('lightning_damage_max'),
default = 0,
},
chaos_damage_min = {
field = 'chaos_damage_min',
type = 'Integer',
func = m_util.cast.factory.number('chaos_damage_min'),
default = 0,
},
chaos_damage_max = {
field = 'chaos_damage_max',
type = 'Integer',
func = m_util.cast.factory.number('chaos_damage_max'),
default = 0,
},
-- armor-type stuff
armour = {
field = 'armour',
type = 'Integer',
func = m_util.cast.factory.number('armour'),
default = 0,
},
energy_shield = {
field = 'energy_shield',
type = 'Integer',
func = m_util.cast.factory.number('energy_shield'),
default = 0,
},
evasion = {
field = 'evasion',
type = 'Integer',
func = m_util.cast.factory.number('evasion'),
default = 0,
},
-- This is the inherent penality from the armour piece if any
movement_speed = {
field = 'movement_speed',
type = 'Integer',
func = m_util.cast.factory.number('movement_speed'),
default = 0,
},
-- shields
block = {
field = 'block',
type = 'Integer',
func = m_util.cast.factory.number('block'),
},
-- skill gem stuff
gem_description = {
field = 'gem_description',
type = 'Text',
func = h.factory.cast_text('gem_description'),
},
dexterity_percent = {
field = 'dexterity_percent',
type = 'Integer',
func = m_util.cast.factory.percentage('dexterity_percent'),
},
strength_percent = {
field = 'strength_percent',
type = 'Integer',
func = m_util.cast.factory.percentage('strength_percent'),
},
intelligence_percent = {
field = 'intelligence_percent',
type = 'Integer',
func = m_util.cast.factory.percentage('intelligence_percent'),
},
primary_attribute = {
field = 'primary_attribute',
type = 'String',
func = function(tpl_args, frame)
for _, attr in ipairs(m_game.constants.attribute_order) do
local val = tpl_args[attr .. '_percent']
if val and val >= 60 then
tpl_args['primary_attribute'] = attr
return
end
end
tpl_args['primary_attribute'] = 'none'
end,
},
gem_tags = {
field = 'gem_tags',
type = 'List (,) of String',
-- TODO: default rework
func = function(tpl_args, frame)
if tpl_args.gem_tags then
tpl_args.gem_tags = m_util.string.split(tpl_args.gem_tags, ',%s*')
end
end,
default = function (tpl_args, frame) return {} end,
},
-- Support gems only
support_gem_letter = {
field = 'support_gem_letter',
type = 'String(size=1)',
func = nil,
},
support_gem_letter_html = {
field = 'support_gem_letter_html',
type = 'Text',
func = function(tpl_args, frame)
if tpl_args.support_gem_letter == nil then
return
end
-- TODO replace this with a loop possibly
local css_map = {
strength = 'red',
intelligence = 'blue',
dexterity = 'green',
}
local id
for k, v in pairs(css_map) do
k = string.format('%s_percent', k)
if tpl_args[k] and tpl_args[k] > 50 then
id = v
break
end
end
if id ~= nil then
local container = mw.html.create('span')
container
:attr('class', string.format('support-gem-id-%s', id))
:wikitext(tpl_args.support_gem_letter)
:done()
tpl_args.support_gem_letter_html = tostring(container)
end
end,
},
--
-- Maps
--
map_tier = {
field = 'tier',
type = 'Integer',
func = m_util.cast.factory.number('map_tier'),
},
map_guild_character = {
field = 'guild_character',
type = 'String(size=1)',
func = nil,
},
map_area_id = {
field = 'area_id',
type = 'String',
func = nil, -- TODO: Validate against a query?
},
map_area_level = {
field = 'area_level',
type = 'Integer',
func = m_util.cast.factory.number('map_area_level'),
},
unique_map_guild_character = {
field = 'unique_guild_character',
type = 'String(size=1)',
func_copy = function(tpl_args, frame)
tpl_args.map_guild_character = tpl_args.unique_map_guild_character
end,
func = nil,
},
unique_map_area_id = {
field = 'unique_area_id',
type = 'String',
func = nil, -- TODO: Validate against a query?
func_copy = function(tpl_args, frame)
tpl_args.map_area_id = tpl_args.unique_map_area_id
end,
},
unique_map_area_level = {
field = 'unique_area_level',
type = 'Integer',
func = m_util.cast.factory.number('unique_map_area_level'),
func_copy = function(tpl_args, frame)
tpl_args.map_area_level = tpl_args.unique_map_area_level
end,
},
map_series = {
field = 'series',
type = 'String',
func = function(tpl_args, frame)
if tpl_args.rarity == 'normal' and tpl_args.map_series == nil then
error(string.format(i18n.errors.generic_required_parameter, 'map_series'))
end
end,
},
-- atlas info is only for the current map series
atlas_x = {
field = 'x',
type = 'Float',
func = m_util.cast.factory.number('atlas_x'),
},
atlas_y = {
field = 'y',
type = 'Float',
func = m_util.cast.factory.number('atlas_y'),
},
atlas_region_id = {
field = 'region_id',
type = 'String',
func = nil,
},
atlas_region_minimum = {
field = 'region_minimum',
type = 'Integer',
func = m_util.cast.factory.number('atlas_region_minimum'),
},
atlas_x0 = {
field = 'x0',
type = 'Float',
func = m_util.cast.factory.number('atlas_x0'),
},
atlas_x1 = {
field = 'x1',
type = 'Float',
func = m_util.cast.factory.number('atlas_x1'),
},
atlas_x2 = {
field = 'x2',
type = 'Float',
func = m_util.cast.factory.number('atlas_x2'),
},
atlas_x3 = {
field = 'x3',
type = 'Float',
func = m_util.cast.factory.number('atlas_x3'),
},
atlas_x4 = {
field = 'x4',
type = 'Float',
func = m_util.cast.factory.number('atlas_x4'),
},
atlas_y0 = {
field = 'y0',
type = 'Float',
func = m_util.cast.factory.number('atlas_0'),
},
atlas_y1 = {
field = 'y1',
type = 'Float',
func = m_util.cast.factory.number('atlas_y1'),
},
atlas_y2 = {
field = 'y2',
type = 'Float',
func = m_util.cast.factory.number('atlas_y2'),
},
atlas_y3 = {
field = 'y3',
type = 'Float',
func = m_util.cast.factory.number('atlas_y3'),
},
atlas_y4 = {
field = 'y4',
type = 'Float',
func = m_util.cast.factory.number('atlas_y4'),
},
atlas_map_tier0 = {
field = 'map_tier0',
type = 'Integer',
func = m_util.cast.factory.number('atlas_map_tier0'),
},
atlas_map_tier1 = {
field = 'map_tier1',
type = 'Integer',
func = m_util.cast.factory.number('atlas_map_tier1'),
},
atlas_map_tier2 = {
field = 'map_tier2',
type = 'Integer',
func = m_util.cast.factory.number('atlas_map_tier2'),
},
atlas_map_tier3 = {
field = 'map_tier3',
type = 'Integer',
func = m_util.cast.factory.number('atlas_map_tier3'),
},
atlas_map_tier4 = {
field = 'map_tier4',
type = 'Integer',
func = m_util.cast.factory.number('atlas_map_tier4'),
},
atlas_connections = {
field = nil,
type = nil,
func = function(tpl_args, frame)
tpl_args.atlas_connections = {}
local cont = true
local i = 1
while cont do
local prefix = string.format('atlas_connection%s_', i)
local regions = tpl_args[prefix .. 'tier']
local data = {
_table = 'atlas_connections',
map1 = string.format('%s (%s)', tpl_args.name, tpl_args.map_series or ''),
map2 = tpl_args[prefix .. 'target'],
}
if regions and data.map2 then
regions = m_util.string.split(regions, ',%s*')
if #regions ~= 5 then
error(string.format(i18n.errors.invalid_region_upgrade_count, i, #regions))
end
for index, value in ipairs(regions) do
data['region' .. (index - 1)] = m_util.cast.boolean(value)
end
tpl_args.atlas_connections[data.map2] = data
table.insert(tpl_args._subobjects, data)
else
cont = false
if i == 1 then
tpl_args.atlas_connections = nil
end
end
i = i + 1
end
end,
default = nil,
},
--
-- Currency-like items
--
stack_size = {
field = 'stack_size',
type = 'Integer',
func = m_util.cast.factory.number('stack_size'),
},
stack_size_currency_tab = {
field = 'stack_size_currency_tab',
type = 'Integer',
func = m_util.cast.factory.number('stack_size_currency_tab'),
},
description = {
field = 'description',
type = 'Text',
func = h.factory.cast_text('description'),
},
cosmetic_type = {
field = 'cosmetic_type',
type = 'String',
func = h.factory.cast_text('cosmetic_type'),
},
-- for essences
is_essence = {
field = nil,
func = m_util.cast.factory.boolean('is_essence'),
default = false,
},
essence_level_restriction = {
field = 'level_restriction',
type = 'Integer',
func = m_util.cast.factory.number('essence_level_restriction'),
},
essence_level = {
field = 'level',
type = 'Integer',
func = m_util.cast.factory.number('essence_level'),
},
essence_type = {
field = 'type',
type = 'Integer',
func = m_util.cast.factory.number('essence_type'),
},
essence_category = {
field = 'category',
type = 'String',
func = nil,
},
-- blight crafting items (i.e. oils)
blight_item_tier = {
field = 'tier',
type = 'Integer',
func = m_util.cast.factory.number('blight_item_tier'),
},
-- harvest seeds
seed_type_id = {
field = 'type_id',
type = 'String',
},
seed_type = {
field = 'type',
type = 'String',
func = function (tpl_args, frame)
if tpl_args.seed_type_id ~= 'none' or tpl_args.seed_type_id ~= nil then
tpl_args.seed_type = m_game.seed_types[tpl_args.seed_type_id]
end
end
},
seed_type_html = {
field = nil,
type = nil,
func = function (tpl_args, frame)
if tpl_args.seed_type ~= nil then
tpl_args.seed_type_html = m_util.html.poe_color(tpl_args.seed_type_id, tpl_args.seed_type)
end
end
},
seed_effect = {
field = 'effect',
type = 'Text',
},
seed_tier = {
field = 'tier',
type = 'Integer',
func = m_util.cast.factory.number('seed_tier'),
},
seed_growth_cycles = {
field = 'growth_cycles',
type = 'Integer',
func = m_util.cast.factory.number('seed_growth_cycles'),
},
seed_required_nearby_seed_tier = {
field = 'required_nearby_seed_tier',
type = 'Integer',
func = m_util.cast.factory.number('seed_required_nearby_seed_tier'),
},
seed_required_nearby_seed_amount = {
field = 'required_nearby_seed_amount',
type = 'Integer',
func = m_util.cast.factory.number('seed_required_nearby_seed_amount'),
},
seed_consumed_wild_lifeforce_percentage = {
field = 'consumed_wild_lifeforce_percentage',
type = 'Integer',
func = m_util.cast.factory.number('seed_consumed_wild_lifeforce_percentage'),
default = 0,
},
seed_consumed_vivid_lifeforce_percentage = {
field = 'consumed_vivid_lifeforce_percentage',
type = 'Integer',
func = m_util.cast.factory.number('seed_consumed_vivid_lifeforce_percentage'),
default = 0,
},
seed_consumed_primal_lifeforce_percentage = {
field = 'consumed_primal_lifeforce_percentage',
type = 'Integer',
func = m_util.cast.factory.number('seed_consumed_primal_lifeforce_percentage'),
default = 0,
},
seed_granted_craft_option_ids = {
field = 'granted_craft_option_ids',
type = 'List (,) of String',
func = m_util.cast.factory.number('seed_grandted_craft_option_ids'),
default = 0,
},
--
-- harvest planet boosters
--
plant_booster_radius = {
field = 'radius',
type = 'Integer',
func = m_util.cast.factory.number('plant_booster_radius'),
},
plant_booster_lifeforce = {
field = 'lifeforce',
type = 'Integer',
func = m_util.cast.factory.number('plant_booster_lifeforce'),
},
plant_booster_additional_crafting_options = {
field = 'additional_crafting_options',
type = 'Integer',
func = m_util.cast.factory.number('plant_booster_additional_crafting_options'),
},
plant_booster_extra_chances = {
field = 'extra_chances',
type = 'Integer',
func = m_util.cast.factory.number('plant_booster_extra_chances'),
},
--
-- Heist properties
--
heist_required_job_id = {
field = 'required_job_id',
type = 'String',
func = h.factory.cast_text('heist_required_job_id'),
},
heist_required_job_level = {
field = 'required_job_level',
type = 'Integer',
func = m_util.cast.factory.number('heist_required_job_level'),
},
heist_data = {
func = function (tpl_args, frame)
if tpl_args.heist_required_job_level then
if tpl_args.heist_required_job_id then
local results = m_cargo.query(
{'heist_jobs', 'heist_npcs', 'heist_npc_skills'},
{'heist_npcs.name', 'heist_jobs.name'},
{
join = 'heist_npc_skills.job_id=heist_jobs.id, heist_npc_skills.npc_id=heist_npcs.id',
where = string.format('heist_npc_skills.job_id = "%s" AND heist_npc_skills.level >= %s', tpl_args.heist_required_job_id, tpl_args.heist_required_job_level),
}
)
local npcs = {}
for _, row in ipairs(results) do
npcs[#npcs+1] = row['heist_npcs.name']
end
tpl_args.heist_required_npcs = table.concat(npcs, ', ')
tpl_args.heist_required_job = results[1]['heist_jobs.name']
else
tpl_args.heist_required_job = i18n.tooltips.heist_any_job
end
end
end,
},
--
-- hideout doodads (HideoutDoodads.dat)
--
is_master_doodad = {
field = 'is_master_doodad',
type = 'Boolean',
func = m_util.cast.factory.boolean('is_master_doodad'),
},
master = {
field = 'master',
type = 'String',
-- todo validate against list of master names
func = m_util.cast.factory.table('master', {key='full', tbl=m_game.constants.masters}),
},
master_level_requirement = {
field = 'level_requirement',
type = 'Integer',
func = m_util.cast.factory.number('master_level_requirement'),
},
master_favour_cost = {
field = 'favour_cost',
type = 'Integer',
func = m_util.cast.factory.number('master_favour_cost'),
},
variation_count = {
field = 'variation_count',
type = 'Integer',
func = m_util.cast.factory.number('variation_count'),
},
-- Propehcy
prophecy_id = {
field = 'prophecy_id',
type = 'String',
func = nil,
},
prediction_text = {
field = 'prediction_text',
type = 'Text',
func = h.factory.cast_text('prediction_text'),
},
seal_cost = {
field = 'seal_cost',
type = 'Integer',
func = m_util.cast.factory.number('seal_cost'),
},
prophecy_reward = {
field = 'reward',
type = 'Text',
func = h.factory.cast_text('prophecy_reward'),
},
prophecy_objective = {
field = 'objective',
type = 'Text',
func = h.factory.cast_text('prophecy_objective'),
},
-- Divination cards
card_art = {
field = 'card_art',
type = 'Page',
func = function(tpl_args, frame)
tpl_args.card_art = string.format(i18n.files.divination_card_art, tpl_args.card_art or tpl_args.name)
end,
},
-- ------------------------------------------------------------------------
-- derived stats
-- ------------------------------------------------------------------------
-- For rarity != normal, rarity already verified
base_item = {
no_copy = true,
field = 'base_item',
type = 'String',
func = function(tpl_args, frame)
tpl_args.base_item = tpl_args.base_item_data['items.name']
end,
},
base_item_id = {
no_copy = true,
field = 'base_item_id',
type = 'String',
func = function(tpl_args, frame)
tpl_args.base_item_id = tpl_args.base_item_data['items.metadata_id']
end,
},
base_item_page = {
no_copy = true,
field = 'base_item_page',
type = 'Page',
func = function(tpl_args, frame)
tpl_args.base_item_page = tpl_args.base_item_data['items._pageName']
end,
},
name_list = {
no_copy = true,
field = 'name_list',
type = 'List (�) of String',
func = function(tpl_args, frame)
if tpl_args.name_list ~= nil then
tpl_args.name_list = m_util.string.split(tpl_args.name_list, ',%s*')
tpl_args.name_list[#tpl_args.name_list+1] = tpl_args.name
else
tpl_args.name_list = {tpl_args.name}
end
end,
},
frame_type = {
no_copy = true,
field = 'frame_type',
type = 'String',
property = nil,
func = function(tpl_args, frame)
if tpl_args._flags.is_prophecy then
tpl_args.frame_type = 'prophecy'
return
end
local var = cfg.class_specifics[tpl_args.class_id]
if var ~= nil and var.frame_type ~= nil then
tpl_args.frame_type = var.frame_type
return
end
if tpl_args.is_relic then
tpl_args.frame_type = 'relic'
return
end
tpl_args.frame_type = tpl_args.rarity_id
end,
},
--
-- args populated by mod validation
--
mods = {
default = function (tpl_args, frame) return {} end,
func_fetch = function (tpl_args, frame)
-- Fetch implicit mods from base item
local results = m_cargo.query(
{'items' ,'item_mods'},
{'item_mods.id', 'item_mods.is_implicit', 'item_mods.is_random', 'item_mods.text'},
{
join = 'items._pageID=item_mods._pageID',
where = string.format('items._pageName="%s" AND item_mods.is_implicit=1', tpl_args.base_item_page),
}
)
for _, row in ipairs(results) do
-- Handle text-only mods
local result
if row['item_mods.id'] == nil then
result = row['item_mods.text']
end
tpl_args._base_implicit_mods[#tpl_args._base_implicit_mods+1] = {
result=result,
id=row['item_mods.id'],
stat_text=row['item_mods.text'],
is_implicit=m_util.cast.boolean(row['item_mods.is_implicit']),
is_random=m_util.cast.boolean(row['item_mods.is_random']),
}
end
end,
},
physical_damage_html = {
no_copy = true,
field = 'physical_damage_html',
type = 'Text',
func = core.factory.damage_html{key='physical'},
},
fire_damage_html = {
no_copy = true,
field = 'fire_damage_html',
type = 'Text',
func = core.factory.damage_html{key='fire'},
},
cold_damage_html = {
no_copy = true,
field = 'cold_damage_html',
type = 'Text',
func = core.factory.damage_html{key='cold'},
},
lightning_damage_html = {
no_copy = true,
field = 'lightning_damage_html',
type = 'Text',
func = core.factory.damage_html{key='lightning'},
},
chaos_damage_html = {
no_copy = true,
field = 'chaos_damage_html',
type = 'Text',
func = core.factory.damage_html{key='chaos'},
},
damage_avg = {
no_copy = true,
field = 'damage_avg',
type = 'Text',
func = function(tpl_args, frame)
local dmg = {min=0, max=0}
for key, _ in pairs(dmg) do
for _, dkey in ipairs(m_game.constants.damage_type_order) do
dmg[key] = dmg[key] + tpl_args[string.format('%s_damage_%s_range_average', dkey, key)]
end
end
dmg = (dmg.min + dmg.max) / 2
tpl_args.damage_avg = dmg
end,
},
damage_html = {
no_copy = true,
field = 'damage_html',
type = 'Text',
func = function(tpl_args, frame)
local text = {}
for _, dkey in ipairs(m_game.constants.damage_type_order) do
local value = tpl_args[dkey .. '_damage_html']
if value ~= nil then
text[#text+1] = value
end
end
if #text > 0 then
tpl_args.damage_html = table.concat(text, '<br>')
end
end,
},
item_limit = {
no_copy = true,
field = 'item_limit',
type = 'Integer',
func = m_util.cast.factory.number('item_limit'),
},
jewel_radius_html = {
no_copy = true,
field = 'radius_html',
type = 'Text',
func = function(tpl_args, frame)
-- Get radius from stats
local radius = tpl_args._stats.local_jewel_effect_base_radius
if radius then
radius = radius.min
local size = m_game.constants.item.jewel_radius_to_size[radius] or radius
local color = radius == 0 and 'mod' or 'value'
tpl_args.jewel_radius_html = m_util.html.poe_color(color, size)
end
end,
},
incubator_effect = {
no_copy = true,
field = 'effect',
type = 'Text',
func = nil,
},
drop_areas_html = {
no_copy = true,
field = 'drop_areas_html',
type = 'Text',
func = function(tpl_args, frame)
if tpl_args.drop_areas_data == nil then
return
end
if tpl_args.drop_areas_html ~= nil then
return
end
local areas = {}
for _, data in pairs(tpl_args.drop_areas_data) do
-- skip legacy maps in the drop html listing
if not string.match(data['areas.id'], '^Map.*') or string.match(data['areas.id'], '^MapWorlds.*') or string.match(data['areas.id'], '^MapAtziri.*') then
areas[#areas+1] = string.format('[[%s|%s]]', data['areas.main_page'] or data['areas._pageName'], data['areas.main_page'] or data['areas.name'])
end
end
tpl_args.drop_areas_html = table.concat(areas, ' • ')
end,
},
release_version = {
no_copy = true,
field = 'release_version',
type = 'String'
},
removal_version = {
no_copy = true,
field = 'removal_version',
type = 'String',
},
--
-- args governing use of the template itself
--
suppress_improper_modifiers_category = {
no_copy = true,
field = nil,
func = m_util.cast.factory.boolean('suppress_improper_modifiers_category'),
default = false,
},
upgraded_from_disabled = {
no_copy = true,
field = nil,
func = m_util.cast.factory.boolean('upgraded_from_disabled'),
default = false,
},
}
core.stat_map = {
required_level_final = {
field = 'required_level',
stats_add = {
'local_level_requirement_+',
},
stats_override = {
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=1, max=1},
},
minimum = 1,
html_fmt_options = {
fmt = '%i',
},
},
weapon_range = {
field = 'weapon_range',
stats_add = {
'local_weapon_range_+',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
physical_damage_min = {
field = 'physical_damage_min',
stats_add = {
'local_minimum_added_physical_damage',
},
stats_increased = {
'local_physical_damage_+%',
'quality',
},
stats_override = {
['local_weapon_no_physical_damage'] = {min=0, max=0},
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
physical_damage_max = {
field = 'physical_damage_max',
stats_add = {
'local_maximum_added_physical_damage',
},
stats_increased = {
'local_physical_damage_+%',
'quality',
},
stats_override = {
['local_weapon_no_physical_damage'] = {min=0, max=0},
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
fire_damage_min = {
field = 'fire_damage_min',
stats_add = {
'local_minimum_added_fire_damage',
},
minimum = 0,
html_fmt_options = {
color = 'fire',
fmt = '%i',
},
},
fire_damage_max = {
field = 'fire_damage_max',
stats_add = {
'local_maximum_added_fire_damage',
},
minimum = 0,
html_fmt_options = {
color = 'fire',
fmt = '%i',
},
},
cold_damage_min = {
field = 'cold_damage_min',
stats_add = {
'local_minimum_added_cold_damage',
},
minimum = 0,
html_fmt_options = {
color = 'cold',
fmt = '%i',
},
},
cold_damage_max = {
field = 'cold_damage_max',
stats_add = {
'local_maximum_added_cold_damage',
},
minimum = 0,
html_fmt_options = {
color = 'cold',
fmt = '%i',
},
},
lightning_damage_min = {
field = 'lightning_damage_min',
stats_add = {
'local_minimum_added_lightning_damage',
},
minimum = 0,
html_fmt_options = {
color = 'lightning',
fmt = '%i',
},
},
lightning_damage_max = {
field = 'lightning_damage_max',
stats_add = {
'local_maximum_added_lightning_damage',
},
minimum = 0,
html_fmt_options = {
color = 'lightning',
fmt = '%i',
},
},
chaos_damage_min = {
field = 'chaos_damage_min',
stats_add = {
'local_minimum_added_chaos_damage',
},
minimum = 0,
html_fmt_options = {
color = 'chaos',
fmt = '%i',
},
},
chaos_damage_max = {
field = 'chaos_damage_max',
stats_add = {
'local_maximum_added_chaos_damage',
},
minimum = 0,
html_fmt_options = {
color = 'chaos',
fmt = '%i',
},
},
critical_strike_chance = {
field = 'critical_strike_chance',
stats_add = {
'local_critical_strike_chance',
},
stats_increased = {
'local_critical_strike_chance_+%',
},
stats_override = {
['local_weapon_always_crit'] = {min=100, max=100},
},
minimum = 0,
html_fmt_options = {
fmt = '%.2f%%',
},
},
attack_speed = {
field = 'attack_speed',
stats_increased = {
'local_attack_speed_+%',
},
minimum = 0,
html_fmt_options = {
fmt = '%.2f',
},
},
flask_life = {
field = 'life',
stats_add = {
'local_flask_life_to_recover',
},
stats_increased = {
'local_flask_life_to_recover_+%',
'local_flask_amount_to_recover_+%',
'quality',
},
html_fmt_options = {
fmt = '%i',
},
},
flask_mana = {
field = 'mana',
stats_add = {
'local_flask_mana_to_recover',
},
stats_increased = {
'local_flask_mana_to_recover_+%',
'local_flask_amount_to_recover_+%',
'quality',
},
},
flask_duration = {
field = 'duration',
stats_increased = {
'local_flask_duration_+%',
-- regular quality isn't used here because it doesn't increase duration of life/mana/hybrid flasks
'quality_flask_duration',
},
stats_increased_inverse = {
'local_flask_recovery_speed_+%',
},
minimum = 0,
html_fmt_options = {
fmt = '%.2f',
},
},
charges_per_use = {
field = 'charges_per_use',
stats_increased = {
'local_charges_used_+%',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
charges_max = {
field = 'charges_max',
stats_add = {
'local_extra_max_charges',
},
stats_increased = {
'local_max_charges_+%',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
block = {
field = 'block',
stats_add = {
'local_additional_block_chance_%',
},
minimum = 0,
html_fmt_options = {
fmt = '%i%%',
},
},
armour = {
field = 'armour',
stats_add = {
'local_base_physical_damage_reduction_rating',
},
stats_increased = {
'local_physical_damage_reduction_rating_+%',
'local_armour_and_energy_shield_+%',
'local_armour_and_evasion_+%',
'local_armour_and_evasion_and_energy_shield_+%',
'quality',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
evasion = {
field = 'evasion',
stats_add = {
'local_base_evasion_rating',
'local_evasion_rating_and_energy_shield',
},
stats_increased = {
'local_evasion_rating_+%',
'local_evasion_and_energy_shield_+%',
'local_armour_and_evasion_+%',
'local_armour_and_evasion_and_energy_shield_+%',
'quality',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
energy_shield = {
field = 'energy_shield',
stats_add = {
'local_energy_shield',
'local_evasion_rating_and_energy_shield',
},
stats_increased = {
'local_energy_shield_+%',
'local_armour_and_energy_shield_+%',
'local_evasion_and_energy_shield_+%',
'local_armour_and_evasion_and_energy_shield_+%',
'quality',
},
stats_override = {
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
required_dexterity = {
field = 'required_dexterity',
stats_add = {
'local_dexterity_requirement_+'
},
stats_increased = {
'local_dexterity_requirement_+%',
'local_attribute_requirements_+%',
},
stats_override = {
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
['local_no_attribute_requirements'] = {min=0, max=0},
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
required_intelligence = {
field = 'required_intelligence',
stats_add = {
'local_intelligence_requirement_+'
},
stats_increased = {
'local_intelligence_requirement_+%',
'local_attribute_requirements_+%',
},
stats_override = {
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
['local_no_attribute_requirements'] = {min=0, max=0},
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
required_strength = {
field = 'required_strength',
stats_add = {
'local_strength_requirement_+'
},
stats_increased = {
'local_strength_requirement_+%',
'local_attribute_requirements_+%',
},
stats_override = {
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
['local_no_attribute_requirements'] = {min=0, max=0},
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
map_area_level = {
field = 'map_area_level',
stats_override = {
['map_item_level_override'] = true,
},
},
}
core.dps_map = {
{
name = 'physical_dps',
field = 'physical_dps',
damage_args = {'physical_damage', },
label_infobox = i18n.tooltips.physical_dps,
html_fmt_options = {
color = 'value',
fmt = '%.1f',
},
},
{
name = 'fire_dps',
field = 'fire_dps',
damage_args = {'fire_damage'},
label_infobox = i18n.tooltips.fire_dps,
html_fmt_options = {
color = 'fire',
fmt = '%.1f',
},
},
{
name = 'cold_dps',
field = 'cold_dps',
damage_args = {'cold_damage'},
label_infobox = i18n.tooltips.cold_dps,
html_fmt_options = {
color = 'cold',
fmt = '%.1f',
},
},
{
name = 'lightning_dps',
field = 'lightning_dps',
damage_args = {'lightning_damage'},
label_infobox = i18n.tooltips.lightning_dps,
html_fmt_options = {
color = 'lightning',
fmt = '%.1f',
},
},
{
name = 'chaos_dps',
field = 'chaos_dps',
damage_args = {'chaos_damage'},
label_infobox = i18n.tooltips.chaos_dps,
html_fmt_options = {
color = 'chaos',
fmt = '%.1f',
},
},
{
name = 'elemental_dps',
field = 'elemental_dps',
damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'},
label_infobox = i18n.tooltips.elemental_dps,
html_fmt_options = {
color = 'value',
fmt = '%.1f',
},
},
{
name = 'poison_dps',
field = 'poison_dps',
damage_args = {'physical_damage', 'chaos_damage'},
label_infobox = i18n.tooltips.poison_dps,
html_fmt_options = {
color = 'value',
fmt = '%.1f',
},
},
{
name = 'dps',
field = 'dps',
damage_args = {'physical_damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'},
label_infobox = i18n.tooltips.dps,
html_fmt_options = {
color = 'value',
fmt = '%.1f',
},
},
}
return core