Module:Item link
This module implements {{item link}} and facilitates the creation of item links.
The above documentation is transcluded from Module:Item link/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.
-- SMW reworked item module
-- ----------------------------------------------------------------------------
-- TODO
-- ----------------------------------------------------------------------------
-- Items
-- -----
-- Check if abyss jewels actually have radius modifiers
--
-- Has explicit mod ids (and others) changing order of values in the API.
--
-- Aggregate ids from Has granted skill id from modifiers
--
-- DROP restriction improvements:
-- drop monster type(s)
--
-- unique items:
-- 3D art
-- supporter attribution
-- option to hide explicit mods or override text
--
-- singular for weapon class in infoboxes
--
--
-- Maps:
-- Area level can be retrieved eventually
--
-- Essence:
-- type column
-- monster modifier info
-- ----------
-- Item table
-- ----------
-- Skills need proper range values in their tables
-- stat_column<i>_stat<j>_id -> make an lua pattern alternative
-- show drop area etc
--
-- ----------
-- Item class
-- ----------
--
-- remove the ul if name_list is not provided
-- maybe smw
-- -----------
-- rework todo
-- -----------
-- check unique map properties copy
-- REMOVED:
-- is_essence
-- RENAMED
-- essence_tier -> essence_level
-- release_version
-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------
local xtable = require('Module:Table')
local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local m_skill = require('Module:Skill')
local m_area = require('Module:Area')
local f_item_link = require('Module:Item link').item_link
local cargo = mw.ext.cargo
local p = {}
local c = {}
c.image_size = 39
c.image_size_full = c.image_size * 2
-- ----------------------------------------------------------------------------
-- Temporary fixes
-- ----------------------------------------------------------------------------
-- Move to util
function m_util.cast.factory.array_table(k, args)
-- Arguments:
-- tbl - table to check against
-- errmsg - error message if no element was found; should accept 1 parameter
args = args or {}
return function (tpl_args, frame)
local elements
if tpl_args[k] ~= nil then
elements = m_util.string.split(tpl_args[k], ',%s*')
for _, element in ipairs(elements) do
local r = m_util.table.find_in_nested_array{value=element, tbl=args.tbl, key='full'}
if r == nil then
error(m_util.html.error{msg=string.format(args.errmsg or i18n.errors.missing_element, element)})
end
end
tpl_args[args.key_out or k] = xtable:new(elements)
end
end
end
function m_util.cast.factory.assoc_table(k, args)
-- Arguments:
--
-- tbl
-- errmsg
-- key_out
return function (tpl_args, frame)
local elements
if tpl_args[k] ~= nil then
elements = m_util.string.split(tpl_args[k], ',%s*')
for _, element in ipairs(elements) do
if args.tbl[element] == nil then
error(m_util.html.error{msg=string.format(args.errmsg or i18n.errors.missing_element, element)})
end
end
tpl_args[args.key_out or k] = elements
end
end
end
function m_util.cargo.query(tables, fields, query, args)
-- Wrapper for mw.ext.cargo.query that helps to work around some bugs
--
-- Current workarounds:
-- field names will be "aliased" to themselves
--
-- Takes 3 arguments:
-- tables - array containing tables
-- fields - array containing fields; these will automatically be renamed to the way they are specified to work around bugs when results are returned
-- query - array containing cargo sql clauses
-- args
-- args.keep_empty
-- Cargo bug workaround
args = args or {}
for i, field in ipairs(fields) do
-- already has some alternate name set, so do not do this.
if string.find(field, '=') == nil then
fields[i] = string.format('%s=%s', field, field)
end
end
local results = cargo.query(table.concat(tables, ','), table.concat(fields, ','), query)
if args.keep_empty == nil then
for _, row in ipairs(results) do
for k, v in pairs(row) do
if v == "" then
row[k] = nil
end
end
end
end
return results
end
function m_util.cargo.array_query(args)
-- Performs a long "OR" query from the given array and field validating that there is only exactly one match returned
--
-- args:
-- tables - array of tables (see util.cargo.query)
-- fields - array of fields (see util.cargo.query)
-- query - array containing cargo sql clauses [optional] (see util.cargo.query)
-- id_array - list of ids to query for
-- id_field - name of the id field, will be automatically added to fields
--
-- RETURN:
-- table - results as given by mw.ext.cargo.query
--
args.query = args.query or {}
args.fields[#args.fields+1] = args.id_field
local id_array = {}
for i, id in ipairs(args.id_array) do
id_array[i] = string.format('%s="%s"', args.id_field, id)
end
if args.query.where then
args.query.where = string.format('(%s) AND (%s)', args.query.where, table.concat(id_array, ' OR '))
else
args.query.where = table.concat(id_array, ' OR ')
end
--
-- Check for duplicates
--
-- The usage of distinct should elimate duplicates here from cargo being bugged while still showing actual data duplicates.
local results = m_util.cargo.query(
args.tables,
{
string.format('COUNT(DISTINCT %s._pageID)=count', args.tables[1]),
args.id_field,
},
{
join=args.query.join,
where=args.query.where,
groupBy=args.id_field,
having=string.format('COUNT(DISTINCT %s._pageID) > 1', args.tables[1]),
}
)
if #results > 0 then
out = {}
for _, row in ipairs(results) do
out[#out+1] = string.format('%s (%s pages found)', row[args.id_field], row['count'])
end
error(string.format('Found duplicates for field "%s":\n %s', args.id_field, table.concat(out, '\n')))
end
--
-- Prepare query
--
if args.query.groupBy then
args.query.groupBy = string.format('%s._pageID,%s', args.tables[1], args.query.groupBy)
else
args.query.groupBy = string.format('%s._pageID', args.tables[1])
end
local results = m_util.cargo.query(
args.tables,
args.fields,
args.query
)
--
-- Check missing results
--
if #results ~= #args.id_array then
local missing = {}
for _, id in ipairs(args.id_array) do
missing[id] = true
end
for _, row in ipairs(results) do
missing[row[args.id_field]] = nil
end
local missing_ids = {}
for k, _ in pairs(missing) do
missing_ids[#missing_ids+1] = k
end
error(string.format('Missing results for "%s" field with values: \n%s', args.id_field, table.concat(missing_ids, '\n')))
end
return results
end
-- ----------------------------------------------------------------------------
-- Strings
-- ----------------------------------------------------------------------------
-- This section contains strings used by this module.
-- Add new strings here instead of in-code directly, this will help other
-- people to correct spelling mistakes easier and help with translation to
-- other PoE wikis.
--
-- TODO: Maybe move this out to a separate sub-page module
local i18n = {
range = '(%s to %s)',
inventory_icon = 'File:%s inventory icon.png',
status_icon = 'File:%s status icon.png',
skill_screenshot = 'File:%s skill screenshot.jpg',
divination_card_art = 'File:%s card art.png',
gem_tag_category = '[[:Category:%s (gem tag)|%s]]',
categories = {
-- maintenance cats
improper_modifiers = 'Items with improper modifiers',
missing_release_version = 'Items without a release version',
broken_upgraded_from_reference = 'Items with broken item references in upgraded from parameters',
-- regular cats
alternate_artwork = 'Items with alternate artwork',
-- misc
gem_tag_affix = '%s (gem tag)',
unique_affix = 'Unique %s',
prophecies = 'Prophecies',
talismans = 'Talismans',
essences = 'Essences',
},
stat_skip_patterns = {
maps = {
'%d+%% increased Quantity of Items found in this Area',
'%d+%% increased Rarity of Items found in this Area',
'%+%d+%% Monster pack size',
-- ranges
'%(%d+%-%d+%)%% increased Quantity of Items found in this Area',
'%(%d+%-%d+%)%% increased Rarity of Items found in this Area',
'%+%(%d+%-%d+%)%% Monster pack size',
},
jewels = {
'Limited to %d+ %(Hidden%)',
'Jewel has a radius of %d+ %(Hidden%)',
},
},
help_text_defaults = {
active_gem = 'Place into an item socket of the right colour to gain this skill. Right click to remove from a socket.',
support_gem = 'This is a Support Gem. It does not grant a bonus to your character, but skills in sockets connected to it. Place into an item socket connected to a socket containing the Active Skill Gem you wish to augment. Right click to remove from a socket.',
hideout_doodad = 'Right click on this item then left click on a location on the ground to create the object.',
jewel = 'Place into an allocated Jewel Socket on the Passive Skill Tree. Right click to remove from the Socket.',
},
-- Used by the item table
item_table = {
item = 'Item',
skill_gem = 'Skill gem',
physical_dps = m_util.html.abbr('pDPS', 'physical damage per second'),
fire_dps = m_util.html.abbr('Fire DPS', 'fire damage per second'),
cold_dps = m_util.html.abbr('Cold DPS', 'cold damage per second'),
lightning_dps = m_util.html.abbr('Light. DPS', 'lightning damage per second'),
chaos_dps = m_util.html.abbr('Chaos DPS', 'chaos damage per second'),
elemental_dps = m_util.html.abbr('eDPS', 'elemental damage (i.e. fire/cold/lightning) per second'),
poison_dps = m_util.html.abbr('Poison DPS', 'poison damage (i.e. physical/chaos) per second'),
dps = m_util.html.abbr('DPS', 'total damage (i.e. physical/fire/cold/lightning/chaos) per second'),
base_item = 'Base Item',
item_class = 'Item Class',
essence_level = 'Essence<br>Level',
drop_level = 'Drop<br>Level',
drop_leagues = 'Drop Leagues',
drop_areas = 'Drop Areas',
drop_text = 'Additional<br>Drop Restrictions',
stack_size = 'Stack<br>Size',
stack_size_currency_tab = m_util.html.abbr('Tab<br>Stack<br>Size', 'Stack size in the currency stash tab'),
armour = m_util.html.abbr('AR', 'Armour'),
evasion = m_util.html.abbr('EV', 'Evasion Rating'),
energy_shield = m_util.html.abbr('ES', 'Energy Shield'),
block = m_util.html.abbr('Block', 'Chance to Block'),
damage = m_util.html.abbr('Damage', 'Colour coded damage'),
attacks_per_second = m_util.html.abbr('APS', 'Attacks per second'),
local_critical_strike_chance = m_util.html.abbr('Crit', 'Local weapon critical strike chance'),
flask_life = m_util.html.abbr('Life', 'Life regenerated over the flask duration'),
flask_mana = m_util.html.abbr('Mana', 'Mana regenerated over the flask duration'),
flask_duration = 'Duration',
flask_charges_per_use = m_util.html.abbr('Usage', 'Number of charges consumed on use'),
flask_maximum_charges = m_util.html.abbr('Capacity', 'Maximum number of flask charges held'),
item_limit = 'Limit',
jewel_radius = 'Radius',
map_tier = 'Map<br>Tier',
map_level = 'Map<br>Level',
map_guild_character = m_util.html.abbr('Char', 'Character for the guild tag'),
buff_effects = 'Buff Effects',
stats = 'Stats',
effects = 'Effect(s)',
flavour_text = 'Flavour Text',
prediction_text = 'Prediction',
help_text = 'Help Text',
seal_cost = m_util.html.abbr('Seal<br>Cost', 'Silver Coin cost of sealing this prophecies into an item'),
objective = 'Objective',
reward = 'Reward',
buff_icon = 'Buff<br>Icon',
-- Skills
support_gem_letter = m_util.html.abbr('L', 'Support gem letter.'),
skill_icon = 'Icon',
description = 'Description',
skill_critical_strike_chance = m_util.html.abbr('Crit', 'Critical Strike Chance'),
cast_time = m_util.html.abbr('Cast<br>Time', 'Casting time of the skill in seconds'),
damage_effectiveness = m_util.html.abbr('Dmg.<br>Eff.', 'Damage Effectiveness'),
mana_cost_multiplier = m_util.html.abbr('MCM', 'Mana cost multiplier - missing values indicate it changes on gem level'),
mana_cost = m_util.html.abbr('Mana', 'Mana cost'),
reserves_mana_suffix = m_util.html.abbr('R', 'reserves mana'),
vaal_souls_requirement = m_util.html.abbr('Souls', 'Vaal souls requirement in Normal/Cruel/Merciless difficulty'),
stored_uses = m_util.html.abbr('Uses', 'Maximum number of stored uses'),
primary_radius = m_util.html.abbr('R1', 'Primary radius'),
secondary_radius = m_util.html.abbr('R2', 'Secondary radius'),
tertiary_radius = m_util.html.abbr('R3', 'Tertiary radius'),
},
-- Used by the item info box
tooltips = {
corrupted = 'Corrupted',
support_icon = 'Icon: %s',
radius = 'Radius: %s',
mana_reserved = 'Mana Reserved: %s',
mana_cost = 'Mana Cost: %s',
mana_multiplier = 'Mana Multiplier: %s',
vaal_souls_per_use = 'Souls per use: %s',
stored_uses = 'Can store %s use(s)',
cooldown_time = 'Cooldown Time: %s',
cast_time = 'Cast Time: %s',
critical_strike_chance = 'Critical Strike Chance: %s',
damage_effectiveness = 'Damage Effectiveness: %s',
projectile_speed = 'Projectile Speed: %s',
quality = 'Quality: %s',
physical_damage = 'Physical Damage: %s',
elemental_damage = 'Elemental Damage:%s',
chaos_damage = 'Chaos Damage: %s',
attacks_per_second = 'Attacks per Second: %s',
weapon_range = 'Weapon Range: %s',
map_level = 'Map Level: %s',
map_tier = 'Map Tier: %s',
map_guild_character = m_util.html.abbr('Guild Character', 'When used in guild creation, this map can be used for the listed character') .. ': %s',
item_quantity = 'Item Quantity: %s',
item_rarity = 'Item Rarity: %s',
monster_pack_size = 'Monster Pack Size: %s',
limited_to = 'Limited to: %s',
flask_mana_recovery = 'Recovers %s Mana over %s seconds',
flask_life_recovery = 'Recovers %s Life over %s seconds',
flask_duration = 'Lasts %s Seconds',
flask_charges_per_use = 'Consumes %s of %s Charges on use',
chance_to_block = 'Chance to Block: %s',
armour = 'Armour: %s',
evasion = 'Evasion: %s',
energy_shield = 'Energy Shield: %s',
talisman_tier = 'Talisman Tier: %s',
stack_size = 'Stack Size: %s',
essence_level = 'Essence Level: %s',
requires = 'Requires %s',
level_inline = 'Level %s',
level = 'Level: %s',
gem_quality = 'Per 1% Quality:',
variation_singular = 'Variation',
variation_plural = 'Variations',
favour_cost = 'Favour cost: %s',
seal_cost = 'Seal cost: <br>%s',
cannot_be_traded_or_modified = 'Cannot be traded or modified',
-- secondary infobox
drop_restrictions = 'Acquisition',
league_restriction = m_util.html.abbr('League(s):', 'Item can be obtained in relation to these league(s)') .. ' %s',
drop_disabled = 'DROP DISABLED',
purchase_costs = m_util.html.abbr('Purchase Costs', 'Cost of purchasing an item of this type at NPC vendors. This does not indicate whether NPCs actually sell the item.'),
sell_price = m_util.html.abbr('Sell Price', 'Items or currency received when selling this item at NPC vendors. Certain vendor recipes may override this value.'),
damage_per_second = 'Weapon DPS',
physical_dps = 'Physical',
fire_dps = 'Fire',
cold_dps = 'Cold',
lightning_dps = 'Lightning',
chaos_dps = 'Chaos',
elemental_dps = 'Elemental',
poison_dps = 'Phys+Chaos',
dps = 'Total',
},
acquisition = {
header = 'Item acquisition',
area = 'This item can be acquired in the following areas:',
upgraded_from = 'This item can be acquired through the following upgrade paths or vendor recipes:',
ingredient_header = 'Usage in recipes',
ingredient = 'This item is used by upgrade paths or vendor recipes to create the following items:',
},
item_class_infobox = {
page = '[[Item class]]',
info = m_util.html.abbr('(?)', 'Item classes categorize items. Classes are often used to restrict items or skill gems to a specific class or by item filters'),
also_referred_to_as = 'Also referred to as:',
},
debug = {
base_item_field_not_found = 'Base item property not found: %s.%s',
field_value_mismatch = 'Value for argument "%s" is set to something else then default: %s',
},
errors = {
missing_base_item = 'Rarity is set to above normal, but base item is not set. A base item for rarities above normal is required!',
missing_rarity = 'Base item parameter is set, but rarity is set to normal. A rarity above normal is required!',
missing_amount = 'Item amount is missing or not a number (%s)',
upgraded_from_broken_reference = 'Item reference in %s is broken (results: %s)',
duplicate_base_items = 'More then one result found for the specified base item. Consider using base_item_page or base_item_id to narrow down the results.',
invalid_league = '%s is not a recognized league',
invalid_tag = '%s is not a valid tag',
generic_argument_parameter = 'Unrecognized %s parameter "%s"',
invalid_item_class = 'Invalid item class',
invalid_item_table_mode = 'Invalid mode for item table',
non_unique_relic = 'Only unique items can be be relics',
},
}
-- ----------------------------------------------------------------------------
-- Other stuff
-- ----------------------------------------------------------------------------
local h = {}
function h.debug(tpl_args, func)
if tpl_args.debug == nil then
return
end
func()
end
function h.na_or_val(tr, value, func)
if value == nil then
tr:wikitext(m_util.html.td.na())
else
local raw_value = value
if func ~= nil then
value = func(value)
end
tr
:tag('td')
:attr('data-sort-value', raw_value)
:wikitext(value)
:done()
end
end
-- helper to loop over the range variables easier
h.range_map = {
min = {
var = '_range_minimum',
},
max = {
var = '_range_maximum',
},
avg = {
var = '_range_average',
},
}
h.range_fields = {
{
field = '_range_minimum',
type = 'Integer',
},
{
field = '_range_maximum',
type = 'Integer',
},
{
field = '_range_average',
type = 'Integer',
},
{
field = '_range_text',
type = 'Text',
},
{
field = '_range_colour',
type = 'String',
},
{
field = '_html',
type = 'Text',
},
}
function h.handle_range_args(tpl_args, frame, argument_key, field, value, fmt_options)
fmt_options = mw.clone(fmt_options)
fmt_options.return_color = true
local html, colour = h.format_value(tpl_args, frame, value, fmt_options)
tpl_args[argument_key .. '_html'] = html
tpl_args[field .. '_html'] = html
tpl_args[field .. '_range_colour'] = colour
fmt_options = mw.clone(fmt_options)
fmt_options.no_color = true
tpl_args[field .. '_range_text'] = h.format_value(tpl_args, frame, value, fmt_options)
end
function h.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
h.stat = {}
function h.stat.add (value, stat_cached)
value.min = value.min + stat_cached.min
value.max = value.max + stat_cached.max
end
function h.stat.more (value, stat_cached)
value.min = value.min * (1 + stat_cached.min / 100)
value.max = value.max * (1 + stat_cached.max / 100)
end
function h.stat.more_inverse (value, stat_cached)
value.min = value.min / (1 + stat_cached.min / 100)
value.max = value.max / (1 + stat_cached.max / 100)
end
h.tbl = {}
function h.tbl.range_fields(field)
return function()
local fields = {}
for _, partial_field in ipairs({'maximum', 'text', 'colour'}) do
fields[#fields+1] = string.format('%s_range_%s', field, partial_field)
end
return fields
end
end
h.tbl.display = {}
function h.tbl.display.na_or_val(tr, value, data)
return h.na_or_val(tr, value)
end
function h.tbl.display.seconds(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('%ss', value)
end)
end
function h.tbl.display.percent(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('%s%%', value)
end)
end
function h.tbl.display.wikilink(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('[[%s]]', value)
end)
end
h.tbl.display.factory = {}
function h.tbl.display.factory.value(args)
args.options = args.options or {}
return function(tr, data, properties)
values = {}
for index, prop in ipairs(properties) do
local value = data[prop]
if args.options[index] and args.options[index].fmt then
value = string.format(args.options[index].fmt, value)
end
values[#values+1] = value
end
local td = tr:tag('td')
td:attr('data-sort-value', table.concat(values, ', '))
td:wikitext(table.concat(values, ', '))
if args.colour then
td:attr('class', 'tc -' .. args.colour)
end
end
end
function h.tbl.display.factory.range(args)
-- args: table
-- property
return function (tr, data, fields)
tr
:tag('td')
:attr('data-sort-value', data[string.format('%s_range_maximum', args.field)] or '0')
:attr('class', 'tc -' .. (data[string.format('%s_range_colour', args.field)] or 'default'))
:wikitext(data[string.format('%s_range_text', args.field)])
:done()
end
end
function h.format_value(tpl_args, frame, value, options)
-- value: table
-- min:
-- max:
-- options: table
-- fmt: formatter to use for the value instead of valfmt
-- fmt_range: formatter to use for the range values. Default: (%s to %s)
-- inline: Use this format string to insert value
-- inline_color: colour to use for the inline value; false to disable colour
-- func: Function to adjust the value with before output
-- color: colour code for m_util.html.poe_color, overrides mod colour
-- no_color: set to true to ingore colour entirely
-- return_color: also return colour
if options.no_color == nil then
if options.color then
value.color = options.color
elseif value.base ~= value.min or value.base ~= value.max then
value.color = 'mod'
else
value.color = 'value'
end
end
if options.func ~= nil then
value.min = options.func(tpl_args, frame, value.min)
value.max = options.func(tpl_args, frame, value.max)
end
if options.fmt == nil then
options.fmt = '%s'
elseif type(options.fmt) == 'function' then
options.fmt = options.fmt(tpl_args, frame)
end
if value.min == value.max then
value.out = string.format(options.fmt, value.min)
else
value.out = string.format(string.format(options.fmt_range or i18n.range, options.fmt, options.fmt), value.min, value.max)
end
if options.no_color == nil then
value.out = m_util.html.poe_color(value.color, value.out)
end
local return_color
if options.return_color ~= nil then
return_color = value.color
end
local text = options.inline
if type(text) == 'string' then
elseif type(text) == 'function' then
text = text(tpl_args, frame)
else
text = nil
end
if text and text ~= '' then
local color
if options.inline_color == nil then
color = 'default'
elseif options.inline_color ~= false then
color = color.inline_color
end
if color ~= nil then
text = m_util.html.poe_color(color, text)
end
return string.format(text, value.out), return_color
end
-- If we didn't return before, return here
return value.out, return_color
end
-- ----------------------------------------------------------------------------
-- core
-- ----------------------------------------------------------------------------
local core = {}
function core.build_cargo_data(tpl_args, frame)
for _, table_name in ipairs(core.item_classes[tpl_args.class].tables) do
for k, field_data in pairs(core.cargo[table_name].fields) do
field_data.table = table_name
for _, stat_data in pairs(core.stat_map) do
if stat_data == k then
for _, range_field in ipairs(h.range_fields) do
local field_name = stat_data.field .. range_field.field
local data = {
no_copy = true,
table = table_name,
field = field_name,
type = range_field.type,
}
core.cargo[table_name].fields[field_name] = data
core.map[field_name] = data
end
break
end
end
if table_name == 'weapons' then
for _, dps_data in ipairs(core.dps_map) do
for _, range_field in ipairs(h.range_fields) do
local field_name = dps_data.field .. range_field.field
local data = {
no_copy = true,
table = table_name,
field = field_name,
type = range_field.type,
}
core.cargo[table_name].fields[field_name] = data
core.map[field_name] = data
end
end
end
end
end
end
function core.validate_mod(tpl_args, frame, args)
-- args:
-- key - implict or explicit
-- i
-- value
local value = tpl_args[args.key .. args.i]
local out = {
result=nil,
modid=nil,
type=args.key,
text=nil,
}
if value ~= nil then
table.insert(tpl_args.mods, value)
table.insert(tpl_args[args.key .. '_mods'], value)
out.modid = value
out.text = tpl_args[args.key .. args.i .. '_text']
--out.result = nil
table.insert(tpl_args._mods, out)
return true
else
value = tpl_args[args.key .. args.i .. '_text']
if value ~= nil then
tpl_args._flags.text_modifier = true
out.result = value
table.insert(tpl_args._mods, out)
return true
end
end
return false
end
function core.process_smw_mods(tpl_args, frame)
tpl_args.sell_prices = {}
if #tpl_args.mods > 0 then
local mods = {}
local mod_ids = {}
for _, mod_data in ipairs(tpl_args._mods) do
if mod_data.result == nil then
mods[mod_data.modid] = mod_data
mod_ids[#mod_ids+1] = mod_data.modid
mods[#mods+1] = string.format('mods.id="%s"', mod_data.modid)
end
end
local results = m_util.cargo.array_query{
tables={'mods'},
fields={'mods._pageName', 'mods.id', 'mods.required_level', 'mods.stat_text'},
id_field='mods.id',
id_array=mod_ids,
}
for _, data in ipairs(results) do
local mod_data = mods[data['mods.id']]
mod_data.result = data
-- update item level requirement
local keys = {'required_level_final'}
-- only update base item requirement if this is an implicit
if mod_data.key == 'implicit' then
keys[#keys+1] = 'required_level'
end
for _, key in ipairs(keys) do
local req = math.floor(tonumber(data['mods.required_level']) * 0.8)
if req > tpl_args[key] then
tpl_args[key] = req
end
end
end
-- fetch stats
results = cargo.query(
'mods,mod_stats',
-- Workaround: Cargo messing up the ids here
'mods.id=mods.id, mod_stats.id=mod_stats.id, mod_stats.min, mod_stats.max',
{
join='mods._pageID=mod_stats._pageID',
where='mod_stats.id IS NOT NULL AND (' .. table.concat(mods, ' OR ') .. ')',
-- Workaround: Fix cargo duplicates
groupBy='mod_stats._pageID, mod_stats.id',
}
)
for _, data in ipairs(results) do
-- Stat subobject
local mod_data = mods[data['mods.id']]
if mod_data.result.stats == nil then
mod_data.result.stats = {data, }
else
mod_data.result.stats[#mod_data.result.stats+1] = data
end
local id = data['mod_stats.id']
local value = {
min = tonumber(data['mod_stats.min']),
max = tonumber(data['mod_stats.max']),
}
value.avg = (value.min+value.max)/2
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], '_stats')
if mod_data.type ~= 'implicit' then
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], '_explicit_stats')
else
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], '_implicit_stats')
end
end
-- fetch sell prices
results = cargo.query(
'mods,mod_sell_prices',
'mods.id, mod_sell_prices.amount, mod_sell_prices.name',
{
join='mods._pageID=mod_sell_prices._pageID',
where='mod_sell_prices.amount IS NOT NULL AND (' .. table.concat(mods, ' OR ') .. ')',
-- Workaround: Fix cargo duplicates
groupBy='mod_sell_prices._pageID, mod_sell_prices.name',
}
)
for _, data in ipairs(results) do
local mod_data = mods[data['mods.id']]
if mod_data.type ~= implicit then
local values = {
name = data['mod_sell_prices.name'],
amount = tonumber(data['mod_sell_prices.amount']),
}
tpl_args.sell_prices[values.name] = (tpl_args.sell_prices[values.name] or 0) + values.amount
end
end
end
local missing_sell_price = true
for _, _ in pairs(tpl_args.sell_prices) do
missing_sell_price = false
break
end
if missing_sell_price then
tpl_args.sell_prices['Scroll Fragment'] = 1
end
-- Set sell price on page
tpl_args.sell_price_order = {}
for name, amount in pairs(tpl_args.sell_prices) do
tpl_args.sell_price_order[#tpl_args.sell_price_order+1] = name
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_sell_prices',
amount = amount,
item_name = name,
}
end
table.sort(tpl_args.sell_price_order)
end
function core.process_base_item(tpl_args, frame, args)
local where
if tpl_args.base_item_id ~= nil then
where = string.format('items.metadata_id="%s"', tpl_args.base_item_id)
elseif tpl_args.base_item_page ~= nil then
where = string.format('items._pageName="%s"' , tpl_args.base_item_page)
elseif tpl_args.base_item ~= nil then
where = string.format('items.name="%s"' , tpl_args.base_item)
elseif tpl_args.rarity ~= 'Normal' then
error(m_util.html.error{msg=i18n.errors.missing_base_item})
else
return
end
if where ~= nil and tpl_args.rarity == 'Normal' then
error(m_util.html.error{msg=i18n.errors.missing_rarity})
end
where = string.format('%s AND items.class="%s" AND items.rarity="Normal"', where, tpl_args.class)
local join = {}
for _, table_name in ipairs(core.item_classes[tpl_args.class].tables) do
if table_name ~= 'items' then
join[#join+1] = string.format('items._pageID=%s._pageID', table_name)
end
end
local fields = {
'items._pageName=items._pageName',
'items.name=items.name',
'items.metadata_id=items.metadata_id',
}
for _, k in ipairs(tpl_args._base_item_args) do
local data = core.map[k]
if data.field ~= nil then
fields[#fields+1] = string.format('%s.%s=%s.%s', data.table, data.field, data.table, data.field)
end
end
local result = cargo.query(
table.concat(core.item_classes[tpl_args.class].tables, ','),
table.concat(fields, ','),
{
where=where,
join=table.concat(join, ','),
-- Workaround: Fix cargo duplicates
groupBy='items._pageID',
}
)
if #result > 1 then
error(m_util.html.error{msg=i18n.errors.duplicate_base_items})
-- TODO be more explicit in the error?
end
result = result[1]
--error(mw.dumpObject(result))
tpl_args.base_item_data = result
core.process_arguments(tpl_args, frame, {array={'base_item', 'base_item_page', 'base_item_id'}})
--Copy values..
for _, k in ipairs(tpl_args._base_item_args) do
local data = core.map[k]
if data.field ~= nil and data.func_fetch == nil then
local value = result[string.format('%s.%s', data.table, data.field)]
-- I can just use data.default since it will be nil if not provided (nil == nil). Neat! ;)
if value ~= "" and (tpl_args[k] == data.default or type(data.default) == 'function') then
tpl_args[k] = value
if data.func ~= nil then
data.func(tpl_args, frame)
end
if data.func_copy ~= nil then
data.func_copy(tpl_args, frame)
end
elseif value == "" then
h.debug(tpl_args, function ()
mw.logObject(string.format(i18n.debug.base_item_field_not_found, data.table, data.field))
end)
elseif tpl_args[k] ~= data.default then
h.debug(tpl_args, function ()
mw.logObject(string.format(i18n.debug.field_value_mismatch, k, tostring(tpl_args[k])))
end)
end
elseif data.func_fetch ~= nil then
data.func_fetch(tpl_args, frame)
end
end
end
function core.process_arguments(tpl_args, frame, args)
for _, k in ipairs(args.array) do
local data = core.map[k]
if data == nil then
error(string.format('Invalid key or missing data for "%s"', k))
end
if data.no_copy == nil then
table.insert(tpl_args._base_item_args, k)
end
if data.func ~= nil then
data.func(tpl_args, frame)
--[[local status, err = pcall(data.func)
-- an error was raised, return the error string instead of the template
if not status then
return err
end
]]--
end
if tpl_args[k] == nil then
if tpl_args.class and core.item_classes[tpl_args.class].defaults ~= nil and core.item_classes[tpl_args.class].defaults[k] ~= nil then
tpl_args[k] = core.item_classes[tpl_args.class].defaults[k]
elseif data.default ~= nil then
if type(data.default) == 'function' then
tpl_args[k] = data.default()
else
tpl_args[k] = data.default
end
end
end
end
end
function core.process_mod_stats(tpl_args, args)
local lines = {}
local skip = core.class_specifics[tpl_args.class]
if skip then
skip = skip.skip_stat_lines
end
for _, modinfo in ipairs(tpl_args._mods) do
if modinfo.type == args.type then
if modinfo.modid == 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
if m_util.cast.boolean(modinfo.text) then
table.insert(lines, modinfo.text)
end
else
for _, line in ipairs(m_util.string.split(modinfo.result['mods.stat_text'], '<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
if #lines == 0 then
return
else
return table.concat(lines, '<br>')
end
end
function core.process_upgraded_from(tpl_args, frame)
local sets = {}
local setid = 1
local set
repeat
local prefix = string.format('upgraded_from_set%s_', setid)
local groupid = 1
local group
set = {
groups = {},
optional = m_util.cast.boolean(tpl_args[prefix .. 'optional']),
text = tpl_args[prefix .. 'text'],
}
repeat
local group_prefix = string.format('%sgroup%s_', prefix, groupid)
group = {
item_name = tpl_args[group_prefix .. 'item_name'],
item_id = tpl_args[group_prefix .. 'item_id'],
item_page = tpl_args[group_prefix .. 'item_page'],
amount = tonumber(tpl_args[group_prefix .. 'amount']),
notes = tpl_args[group_prefix .. 'notes'],
}
if group.item_name ~= nil or group.item_id ~= nil or group.item_page ~= nil then
if group.amount == nil then
error(string.format(i18n.errors.missing_amount, group_prefix .. 'amount'))
else
-- for verification purposes
local where
if group.item_id then
where = string.format('items.metadata_id="%s"', group.item_id)
elseif group.item_page then
where = string.format('items._pageName="%s"', group.item_page)
elseif group.item_name then
where = string.format('items.name="%s"', group.item_name)
end
local results = cargo.query(
'items',
'items._pageName, items.name, items.metadata_id',
{
where=where,
-- Workaround: Fix cargo duplicates
groupBy='items._pageID',
}
)
if #results ~= 1 then
tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.upgraded_from_broken_reference, string.sub(group_prefix, 1, -2), #results)
tpl_args._flags.broken_upgraded_from_reference = true
else
results = results[1]
if results['items.metadata_id'] ~= '' then
group.item_id = results['items.metadata_id']
end
group.item_name = results['items.name']
group.page = results['items._pageName']
set.groups[#set.groups+1] = group
end
end
end
groupid = groupid + 1
until group.item_name == nil and group.item_id == nil and group.item_page == nil
-- set was empty, can terminate safely
if #set.groups == 0 then
set = nil
else
setid = setid + 1
sets[#sets+1] = set
end
until set == nil
if #sets == 0 then
return
end
tpl_args.upgrade_from_sets = sets
-- set upgraded_from data
for i, set in ipairs(sets) do
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'upgraded_from_sets',
set_id = i,
text = set.text,
--'optional' = set.optional,
}
for j, group in ipairs(set.groups) do
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'upgraded_from_groups',
group_id = j,
set_id = i,
item_name = group.item_name,
item_id = group.item_id,
item_page = group.page,
amount = group.amount,
notes = group.notes,
}
end
end
end
--
-- function factory
--
core.factory = {}
function core.factory.display_value(args)
-- args:
-- type: Type of the keys (nil = regular, gem = skill gems, stat = stats)
-- options<Array>:
-- key: key to use
-- allow_zero: allow zero values
-- hide_default: hide the value if this is set
-- hide_default_key: key to use if it isn't equal to the key parameter
-- -- from h.format_value --
-- fmt: formatter to use for the value instead of valfmt
-- fmt_range: formatter to use for the range values. Default: (%s to %s)
-- insert: insert results into this object
-- func: Function to adjust the value with before output
-- color: colour code for m_util.html.poe_color, overrides mod colour
-- no_color: set to true to ingore colour entirely
for k, default in pairs({options = {}}) do
if args[k] == nil then
args[k] = default
end
end
return function (tpl_args, frame)
local base_values = {}
local temp_values = {}
if args.type == 'gem' then
if not core.class_groups.gems.keys[tpl_args.class] then
return
end
for i, data in ipairs(args.options) do
local value = tpl_args.skill_levels[0][data.key]
if value ~= nil then
base_values[#base_values+1] = value
temp_values[#temp_values+1] = {value={min=value,max=value}, index=i}
else
value = {
min=tpl_args.skill_levels[1][data.key],
max=tpl_args.skill_levels[tpl_args.max_level][data.key],
}
if value.min == nil or value.max == nil then
else
base_values[#base_values+1] = value.min
temp_values[#temp_values+1] = {value=value, index=i}
end
end
end
elseif args.type == 'stat' then
for i, data in ipairs(args.options) 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
for i, data in ipairs(args.options) 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.options[data.index]
local insert = false
if opt.hide_default == nil then
insert = true
elseif opt.hide_default_key == nil then
local v = data.value
if opt.hide_default ~= v.min and opt.hide_default ~= v.max then
insert = true
end
else
local v = {
min = tpl_args[opt.hide_default_key .. '_range_minimum'],
max = tpl_args[opt.hide_default_key .. '_range_maximum'],
}
if v.min == nil or v.max == nil then
if opt.hide_default ~= tpl_args[opt.hide_default_key] then
insert = true
end
elseif opt.hide_default ~= v.min and opt.hide_default ~= v.max then
insert = true
end
end
if insert == true then
table.insert(final_values, data)
end
end
-- all zeros = dont display and return early
if #final_values == 0 then
return nil
end
local out = {}
for i, data in ipairs(final_values) do
local value = data.value
value.base = base_values[data.index]
local options = args.options[data.index]
if options.color == nil and args.type == 'gem' then
value.color = 'value'
end
out[#out+1] = h.format_value(tpl_args, frame, value, options)
end
if args.inline then
return m_util.html.poe_color('default', string.format(args.inline, unpack(out)))
else
return table.concat(out, '')
end
end
end
function core.factory.display_value_only(key)
return function(tpl_args, frame)
return tpl_args[key]
end
end
function core.factory.descriptor_value(args)
-- Arguments:
-- key
-- tbl
args = args or {}
return function (tpl_args, frame, value)
args.tbl = args.tbl or tpl_args
if args.tbl[args.key] then
value = m_util.html.abbr(value, args.tbl[args.key])
end
return value
end
end
function core.factory.damage_html(args)
return function(tpl_args, frame)
if args.key ~= 'physical' then
args.color = args.key
args.no_color = true
end
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.display_value{options={ [1] = {
key = key,
no_color = args.no_color,
hide_default = 0
}}}(tpl_args, frame)
end
if value.min and value.max then
value = value.min .. '–' .. value.max
if args.color ~= nil then
value = m_util.html.poe_color(args.color, value)
end
tpl_args[args.key .. '_damage_html'] = value
end
end
end
core.display = {}
function core.display.add_to_container_from_map(tpl_args, frame, container, mapping)
local grpcont
local valid
local statcont = mw.html.create('span')
statcont
:attr('class', 'item-stats')
:done()
local count = 0
for _, group in ipairs(mapping) do
grpcont = {}
if group.func == nil then
for _, disp in ipairs(group) do
valid = true
-- No args to verify which means always valid
if disp.args == nil then
elseif type(disp.args) == 'table' then
for _, key in ipairs(disp.args) do
if tpl_args[key] == nil then
valid = false
break
end
end
elseif type(disp.args) == 'function' then
valid = disp.args(tpl_args, frame)
end
if valid then
grpcont[#grpcont+1] = disp.func(tpl_args, frame)
end
end
else
grpcont = group.func(tpl_args, frame)
end
if #grpcont > 0 then
count = count + 1
local header = ''
if group.header == nil then
elseif type(group.header) == 'function' then
header = group.header()
else
header = string.format('<em class="header">%s</em><br>', group.header)
end
statcont
:tag('span')
:attr('class', 'group ' .. (group.css_class or ''))
:wikitext(header .. table.concat(grpcont, '<br>'))
:done()
end
end
-- Don't add empty containers
if count > 0 then
container:node(statcont)
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,
},
implicit_stat_text = {
field = 'implicit_stat_text',
type = 'Text',
func = function(tpl_args, frame)
tpl_args.implicit_stat_text = core.process_mod_stats(tpl_args, {type='implicit'})
end,
},
explicit_stat_text = {
field = 'explicit_stat_text',
type = 'Text',
func = function(tpl_args, frame)
tpl_args.explicit_stat_text = core.process_mod_stats(tpl_args, {type='explicit'})
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 = m_util.html.poe_color('corrupted', i18n.tooltips.corrupted)
else
tpl_args.explicit_stat_text = (tpl_args.explicit_stat_text or '') .. '<br>' .. m_util.html.poe_color('corrupted', 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,
},
-- processed in core.build_item_classes
class = {
no_copy = true,
field = 'class',
type = 'String',
func = m_util.cast.factory.table('class', {key='full', tbl=m_game.constants.item.class}),
},
-- generic
rarity = {
no_copy = true,
field = 'rarity',
type = 'String',
func = m_util.cast.factory.table('rarity', {key={'full', 'long_lower'}, tbl=m_game.constants.item.rarity, rtrkey='full'}),
},
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_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
return
end
tpl_args.drop_areas = m_util.string.split(tpl_args.drop_areas, ',%s*')
tpl_args.drop_areas_data = m_util.cargo.array_query{
tables={'areas'},
fields={'areas._pageName', 'areas.name', 'areas.main_page'},
id_field='areas.id',
id_array=tpl_args.drop_areas,
}
-- TODO: Cargo: Raise error on missing
local areas = {}
for _, row in pairs(tpl_args.drop_areas) do
areas[#areas+1] = row['area.name']
end
end,
},
drop_text = {
no_copy = true,
field = 'drop_text',
type = 'String',
},
required_level = {
field = 'required_level_base',
type = 'Integer',
func = m_util.cast.factory.number('required_level'),
default = 1,
},
required_level_final = {
field = 'required_level',
type = 'Integer',
func = function(tpl_args, frame)
tpl_args.required_level_final = tpl_args.required_level
end,
default = 1,
},
required_dexterity = {
field = 'required_dexterity',
type = 'Integer',
func = m_util.cast.factory.number('required_dexterity'),
default = 0,
},
required_strength = {
field = 'required_strength',
type = 'Integer',
func = m_util.cast.factory.number('required_strength'),
default = 0,
},
required_intelligence = {
field = 'required_intelligence',
type = 'Integer',
func = m_util.cast.factory.number('required_intelligence'),
default = 0,
},
inventory_icon = {
no_copy = true,
field = 'inventory_icon',
type = 'String',
func = function(tpl_args, frame)
if tpl_args.class == 'Divination Card' then
tpl_args.inventory_icon = tpl_args.inventory_icon or 'Divination card'
end
tpl_args.inventory_icon_id = tpl_args.inventory_icon or tpl_args.name
tpl_args.inventory_icon = string.format(i18n.inventory_icon, tpl_args.inventory_icon_id)
end,
},
-- note: this must be called after inventory item 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.inventory_icon, string.format('%s %s', tpl_args.inventory_icon_id, name))
end
end
tpl_args.alternate_art_inventory_icons = icons
end,
default = function () 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 = {
field = 'help_text',
type = 'Text',
func = nil,
},
flavour_text = {
no_copy = true,
field = 'flavour_text',
type = 'Text',
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',
func = nil,
},
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 ~= 'Unique' then
error(i18n.errors.non_unique_relic)
end
end,
default = false,
},
purchase_costs = {
func = function(tpl_args, frame)
local purchase_costs = {}
for _, rarity_names in ipairs(m_game.constants.item.rarity) do
local rtbl = {}
local prefix = string.format('purchase_cost_%s', rarity_names.long_lower)
local i = 1
while i ~= -1 do
prefix = prefix .. i
local values = {
name = tpl_args[prefix .. '_name'],
amount = tonumber(tpl_args[prefix .. '_amount']),
rarity = rarity_names.long_upper,
}
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_names.long_lower] = rtbl
end
tpl_args.purchase_costs = purchase_costs
end,
func_fetch = function(tpl_args, frame)
if tpl_args.rarity ~= 'Unique' then
return
end
local results = cargo.query(
'items, item_purchase_costs',
'item_purchase_costs.amount=item_purchase_costs.amount,item_purchase_costs.name=item_purchase_costs.name,item_purchase_costs.rarity=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),
-- Workaround: Fix cargo duplicates
groupBy = 'item_purchase_costs._pageName, item_purchase_costs.name, item_purchase_costs.rarity',
}
)
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,
},
--
-- 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 ~= 'Unique' then
quality = 0
elseif core.class_groups.weapons.keys[tpl_args.class] or core.class_groups.armor.keys[tpl_args.class] then
quality = 20
else
quality = 0
end
end
tpl_args.quality = quality
local stat = {
min = quality,
max = quality,
avg = quality,
}
h.stats_update(tpl_args, 'quality', stat, nil, '_stats')
if tpl_args.class == 'Utility Flasks' or tpl_args.class == 'Critical Utility Flasks' then
h.stats_update(tpl_args, 'quality_flask_duration', stat, nil, '_stats')
-- quality is added to quantity for maps
elseif tpl_args.class == 'Maps' then
h.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 = 'Integer',
func = m_util.cast.factory.number('flask_duration'),
},
buff_id = {
field = 'id',
type = 'String',
func = nil,
},
buff_values = {
field = '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
tpl_args.buff_values = values
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.status_icon, tpl_args.name)
end,
},
-- weapons
critical_strike_chance = {
field = 'critical_strike_chance',
type = 'Integer',
func = m_util.cast.factory.number('critical_strike_chance'),
},
attack_speed = {
field = 'attack_speed',
type = 'Integer',
func = m_util.cast.factory.number('attack_speed'),
},
range = {
field = 'range',
type = 'Integer',
func = m_util.cast.factory.number('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,
},
-- shields
block = {
field = 'block',
type = 'Integer',
func = m_util.cast.factory.number('block'),
},
-- skill gem stuff
gem_description = {
field = 'gem_description',
type = 'Text',
func = nil,
},
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.attributes) do
local val = tpl_args[attr.long_lower .. '_percent']
if val and val >= 60 then
tpl_args['primary_attribute'] = attr.long_upper
return
end
end
tpl_args['primary_attribute'] = 'None'
end,
},
gem_tags = {
field = 'gem_tags',
type = 'List (,) of String',
-- TODO: default rework
func = m_util.cast.factory.array_table('gem_tags', {
tbl = m_game.constants.item.gem_tags,
errmsg = i18n.errors.invalid_tag,
}),
default = function () return {} end,
},
skill_screenshot = {
field = 'skill_screenshot',
type = 'Page',
func = function(tpl_args, frame)
tpl_args.skill_screenshot = string.format(i18n.skill_screenshot, tpl_args.name)
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('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 = {
ield = '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,
},
--
-- 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 = nil,
},
cosmetic_type = {
field = 'cosmetic_type',
type = 'String',
func = nil,
},
-- 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'),
},
--
-- 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 = nil,
},
seal_cost = {
field = 'seal_cost',
type = 'Integer',
func = m_util.cast.factory.number('seal_cost'),
},
prophecy_reward = {
field = 'reward',
type = 'Text',
func = nil,
},
prophecy_objective = {
field = 'objective',
type = 'Text',
func = nil,
},
-- Divination cards
card_art = {
field = 'card_art',
type = 'Page',
func = function(tpl_args, frame)
tpl_args.card_art = string.format(i18n.divination_card_art, 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.name == 'Prophecy' or tpl_args.base_item == 'Prophecy' then
tpl_args.frame_type = 'prophecy'
return
end
local var = core.class_specifics[tpl_args.class]
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 = string.lower(tpl_args.rarity)
end,
},
--
-- args populated by mod validation
--
mods = {
no_copy = true,
field = 'mods',
type = 'List (,) of String',
default = function () return {} end,
},
implicit_mods = {
field = 'implicit_mods',
type = 'List (,) of String',
func_copy = function (tpl_args)
tpl_args.implicit_mods = m_util.string.split(tpl_args.implicit_mods, ',%s*')
for _, modid in ipairs(tpl_args.implicit_mods) do
tpl_args.mods[#tpl_args.mods+1] = modid
tpl_args._mods[#tpl_args._mods+1] = {
result=nil,
modid=modid,
type='implicit',
}
end
end,
default = function () return {} end,
},
explicit_mods = {
field = 'explicit_mods',
type = 'List (,) of String',
default = function () return {} 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 _, data in ipairs(m_game.constants.damage_types) do
dmg[key] = dmg[key] + tpl_args[string.format('%s_damage_%s_range_average', data.short_lower, 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 _, data in ipairs(m_game.constants.damage_types) do
local value = tpl_args[data.short_lower .. '_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)
local radius = tpl_args._stats.local_jewel_effect_base_radius
if radius then
radius = radius.min
tpl_args.jewel_radius_html = string.format('%s (%i)', (m_game.constants.item.jewel_radius_to_size[radius] or '?'), radius)
end
end,
},
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
local page
if data['areas.main_page'] == '' then
page = data['areas._pageName']
else
page = data['areas.main_page']
end
areas[#areas+1] = string.format('[[%s]]', page)
end
tpl_args.drop_areas_html = table.concat(areas, ', ')
end,
},
}
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',
},
},
range = {
field = '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',
},
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',
},
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',
},
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'
},
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},
},
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},
},
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},
},
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 = i18n.item_table.physical_dps,
label_infobox = i18n.tooltips.physical_dps,
html_fmt_options = {
color = 'value',
fmt = '%.1f',
},
},
{
name = 'fire_dps',
field = 'fire_dps',
damage_args = {'fire_damage'},
label = i18n.item_table.fire_dps,
label_infobox = i18n.tooltips.fire_dps,
html_fmt_options = {
color = 'fire',
fmt = '%.1f',
},
},
{
name = 'cold_dps',
field = 'cold_dps',
damage_args = {'cold_damage'},
label = i18n.item_table.cold_dps,
label_infobox = i18n.tooltips.cold_dps,
html_fmt_options = {
color = 'cold',
fmt = '%.1f',
},
},
{
name = 'lightning_dps',
field = 'lightning_dps',
damage_args = {'lightning_damage'},
label = i18n.item_table.lightning_dps,
label_infobox = i18n.tooltips.lightning_dps,
html_fmt_options = {
color = 'lightning',
fmt = '%.1f',
},
},
{
name = 'chaos_dps',
field = 'chaos_dps',
damage_args = {'chaos_damage'},
label = i18n.item_table.chaos_dps,
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 = i18n.item_table.elemental_dps,
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 = i18n.item_table.poison_dps,
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 = i18n.item_table.dps,
label_infobox = i18n.tooltips.dps,
html_fmt_options = {
color = 'value',
fmt = '%.1f',
},
},
}
core.cargo = {}
core.cargo.items = {
table = 'items',
fields = {
html = core.map.html,
implicit_stat_text = core.map.implicit_stat_text,
explicit_stat_text = core.map.explicit_stat_text,
stat_text = core.map.stat_text,
class = core.map.class,
rarity = core.map.rarity,
name = core.map.name,
size_x = core.map.size_x,
size_y = core.map.size_y,
drop_enabled = core.map.drop_enabled,
drop_level = core.map.drop_level,
drop_level_maximum = core.map.drop_level_maximum,
drop_leagues = core.map.drop_leagues,
drop_areas = core.map.drop_areas,
drop_areas_html = core.map.drop_areas_html,
drop_text = core.map.drop_text,
required_level = core.map.required_level,
required_level_final = core.map.required_level_final,
required_dexterity = core.map.required_dexterity,
required_strength = core.map.required_strength,
required_intelligence = core.map.required_intelligence,
inventory_icon = core.map.inventory_icon,
alternate_art_inventory_icons = core.map.alternate_art_inventory_icons,
buff_icon = core.map.buff_icon,
cannot_be_traded_or_modified = core.map.cannot_be_traded_or_modified,
help_text = core.map.help_text,
flavour_text = core.map.flavour_text,
tags = core.map.tags,
metadata_id = core.map.metadata_id,
is_corrupted = core.map.is_corrupted,
is_relic = core.map.is_relic,
quality = core.map.quality,
base_item = core.map.base_item,
base_item_id = core.map.base_item_id,
base_item_page = core.map.base_item_page,
frame_type = core.map.frame_type,
mods = core.map.mods,
explicit_mods = core.map.explicit_mods,
implicit_mods = core.map.implicit_mods,
name_list = core.map.name_list,
description = core.map.description,
},
}
core.cargo.item_purchase_costs = {
table = 'item_purchase_costs',
fields = {
amount = {
field = 'amount',
type = 'Integer',
},
name = {
field = 'name',
type = 'String',
},
rarity = {
field = 'rarity',
type = 'String',
},
},
}
core.cargo.item_stats = {
table = 'item_stats',
fields = {
id = {
field = 'id',
type = 'String',
},
min = {
field = 'min',
type = 'Integer',
},
max = {
field = 'max',
type = 'Integer',
},
avg = {
field = 'avg',
type = 'Integer',
},
is_implicit = {
field = 'is_implicit',
type = 'Boolean',
},
},
}
-- There probably will be a table named "buffs" in the future, so "item_buffs" is the best solution here
core.cargo.item_buffs = {
table = 'item_buffs',
fields = {
id = core.map.buff_id,
values = core.map.buff_values,
stat_text = core.map.buff_stat_text,
icon = core.map.buff_icon,
},
}
core.cargo.upgraded_from_sets = {
table = 'upgraded_from_sets',
fields = {
set_id = {
field = 'set_id',
type = 'Integer',
},
text = {
field = 'text',
type = 'Text',
},
}
}
core.cargo.upgraded_from_groups = {
table = 'upgraded_from_groups',
fields = {
group_id = {
field = 'group_id',
type = 'Integer',
},
set_id = {
field = 'set_id',
type = 'Integer',
},
item_id = {
field = 'item_id',
type = 'String',
},
item_name = {
field = 'item_name',
type = 'String',
},
item_page = {
field = 'item_page',
type = 'Page',
},
integer = {
field = 'amount',
type = 'Integer',
},
notes = {
field = 'notes',
type = 'Text',
},
}
}
core.cargo.amulets = {
table = 'amulets',
fields = {
is_talisman = core.map.is_talisman,
talisman_tier = core.map.talisman_tier,
},
}
core.cargo.flasks = {
table = 'flasks',
fields = {
-- All flasks
duration = core.map.flask_duration,
charges_max = core.map.charges_max,
charges_per_use = core.map.charges_per_use,
-- Life/Mana/Hybrid flasks
life = core.map.flask_life,
mana = core.map.flask_mana,
},
}
core.cargo.weapons = {
table = 'weapons',
fields = {
critical_strike_chance = core.map.critical_strike_chance,
attack_speed = core.map.attack_speed,
range = core.map.range,
physical_damage_min = core.map.physical_damage_min,
physical_damage_max = core.map.physical_damage_max,
physical_damage_html = core.map.physical_damage_html,
fire_damage_html = core.map.fire_damage_html,
cold_damage_html = core.map.cold_damage_html,
lightning_damage_html = core.map.lightning_damage_html,
chaos_damage_html = core.map.chaos_damage_html,
damage_avg = core.map.damage_avg,
damage_html = core.map.damage_html,
-- Values added via stat population
fire_damage_min = core.map.fire_damage_min,
fire_damage_max = core.map.fire_damage_max,
cold_damage_min = core.map.cold_damage_min,
cold_damage_max = core.map.cold_damage_max,
lightning_damage_min = core.map.lightning_damage_min,
lightning_damage_max = core.map.lightning_damage_max,
chaos_damage_min = core.map.chaos_damage_min,
chaos_damage_max = core.map.chaos_damage_max,
},
}
core.cargo.armours = {
table = 'armours',
fields = {
armour = core.map.armour,
energy_shield = core.map.energy_shield,
evasion = core.map.evasion,
},
}
core.cargo.shields = {
table = 'shields',
fields = {
block = core.map.block,
}
}
core.cargo.skill_gems = {
table = 'skill_gems',
fields = {
gem_description = core.map.gem_description,
dexterity_percent = core.map.dexterity_percent,
strength_percent = core.map.strength_percent,
intelligence_percent = core.map.intelligence_percent,
primary_attribute = core.map.primary_attribute,
gem_tags = core.map.gem_tags,
skill_screenshot = core.map.skill_screenshot,
-- Support Skill Gems
support_gem_letter = core.map.support_gem_letter,
support_gem_letter_html = core.map.support_gem_letter_html,
},
}
core.cargo.maps = {
table = 'maps',
fields = {
tier = core.map.map_tier,
guild_character = core.map.map_guild_character,
area_id = core.map.map_area_id,
unique_area_id = core.map.unique_map_area_id,
-- REMOVE?
area_level = core.map.map_area_level,
unique_area_level = core.map.unique_map_area_level,
},
}
core.cargo.stackables = {
table = 'stackables',
fields = {
stack_size = core.map.stack_size,
stack_size_currency_tab = core.map.stack_size_currency_tab,
cosmetic_type = core.map.cosmetic_type,
},
}
core.cargo.essences = {
table = 'essences',
fields = {
level_restriction = core.map.essence_level_restriction,
level = core.map.essence_level,
},
}
core.cargo.hideout_doodads = {
table = 'hideout_doodads',
fields = {
is_master_doodad = core.map.is_master_doodad,
master = core.map.master,
master_level_requirement = core.map.master_level_requirement,
master_favour_cost = core.map.master_favour_cost,
variation_count = core.map.variation_count,
},
}
core.cargo.prophecies = {
table = 'prophecies',
fields = {
prophecy_id = core.map.prophecy_id,
prediction_text = core.map.prediction_text,
seal_cost = core.map.seal_cost,
objective = core.map.prophecy_objective,
reward = core.map.prophecy_reward,
},
}
core.cargo.divination_cards = {
table = 'divination_cards',
fields = {
card_art = core.map.card_art,
},
}
core.cargo.jewels = {
table = 'jewels',
fields = {
item_limit = core.map.item_limit,
radius_html = core.map.jewel_radius_html,
},
}
-- TODO: Second pass for i18n item classes
-- base item is default, but will be validated later
-- Notes:
-- inventory_icon must always be before alternate_art_inventory_icons
-- is_relic after rarity
core.default_args = {
'rarity', 'name', 'name_list', 'size_x', 'size_y',
'drop_enabled', 'drop_level', 'drop_level_maximum', 'drop_leagues', 'drop_areas', 'drop_text', 'required_level', 'required_level_final',
'inventory_icon', 'alternate_art_inventory_icons', 'flavour_text',
'cannot_be_traded_or_modified', 'help_text', 'tags', 'metadata_id', 'is_corrupted', 'is_relic', 'purchase_costs', 'mods', 'implicit_mods', 'explicit_mods',
'drop_areas_html',
}
-- frame_type is needed in stat_text
core.late_args = {'frame_type', 'implicit_stat_text', 'explicit_stat_text', 'stat_text'}
core.prophecy_args = {'prophecy_id', 'prediction_text', 'seal_cost', 'prophecy_objective', 'prophecy_reward'}
core.tables = {'items'}
core.class_groups = {
flasks = {
tables = {'flasks'},
keys = {['Life Flasks'] = true, ['Mana Flasks'] = true, ['Hybrid Flasks'] = true, ['Utility Flasks'] = true, ['Critical Utility Flasks'] = true},
args = {'quality', 'flask_duration', 'charges_max', 'charges_per_use'},
},
weapons = {
tables = {'weapons'},
keys = {['Claws'] = true, ['Daggers'] = true, ['Wands'] = true, ['One Hand Swords'] = true, ['Thrusting One Hand Swords'] = true, ['One Hand Axes'] = true, ['One Hand Maces'] = true, ['Bows'] = true, ['Staves'] = true, ['Two Hand Swords'] = true, ['Two Hand Axes'] = true, ['Two Hand Maces'] = true, ['Sceptres'] = true, ['Fishing Rods'] = true},
args = {'quality', 'required_dexterity', 'required_intelligence', 'required_strength', 'critical_strike_chance', 'attack_speed', 'physical_damage_min', 'physical_damage_max', 'lightning_damage_min', 'lightning_damage_max', 'cold_damage_min', 'cold_damage_max', 'fire_damage_min', 'fire_damage_max', 'chaos_damage_min', 'chaos_damage_max', 'range'},
late_args = {'physical_damage_html', 'fire_damage_html', 'cold_damage_html', 'lightning_damage_html', 'chaos_damage_html', 'damage_avg', 'damage_html'},
},
gems = {
tables = {'skill_gems'},
keys = {['Active Skill Gems'] = true, ['Support Skill Gems'] = true},
args = {'dexterity_percent', 'strength_percent', 'intelligence_percent', 'primary_attribute', 'gem_tags'},
},
armor = {
tables = {'armours'},
keys = {['Gloves'] = true, ['Boots'] = true, ['Body Armours'] = true, ['Helmets'] = true, ['Shields'] = true},
args = {'quality', 'required_dexterity', 'required_intelligence', 'required_strength', 'armour', 'energy_shield', 'evasion'},
},
stackable = {
tables = {'stackables'},
keys = {['Currency'] = true, ['Stackable Currency'] = true, ['Hideout Doodads'] = true, ['Microtransactions'] = true, ['Divination Card'] = true},
args = {'stack_size', 'stack_size_currency_tab', 'description', 'cosmetic_type'},
},
}
core.class_specifics = {
['Amulets'] = {
args = {'is_talisman', 'talisman_tier'},
},
['Life Flasks'] = {
args = {'flask_life'},
},
['Mana Flasks'] = {
args = {'flask_mana'},
},
['Hybrid Flasks'] = {
args = {'flask_life', 'flask_mana'},
},
['Utility Flasks'] = {
tables = {'item_buffs'},
args = {'buff_id', 'buff_values', 'buff_stat_text', 'buff_icon'},
},
['Critical Utility Flasks'] = {
tables = {'item_buffs'},
args = {'buff_id', 'buff_values', 'buff_stat_text', 'buff_icon'},
},
['Active Skill Gems'] = {
args = {'skill_screenshot'},
defaults = {
help_text = i18n.help_text_defaults.active_gem,
size_x = 1,
size_y = 1,
},
frame_type = 'gem',
},
['Support Skill Gems'] = {
args = {'support_gem_letter', 'support_gem_letter_html'},
defaults = {
help_text = i18n.help_text_defaults.support_gem,
size_x = 1,
size_y = 1,
},
frame_type = 'gem',
},
['Shields'] = {
tables = {'shields'},
args = {'block'},
},
['Maps'] = {
tables = {'maps'},
args = {'quality', 'map_tier', 'map_guild_character', 'map_area_id', 'map_area_level', 'unique_map_area_id', 'unique_map_area_level', 'unique_map_guild_character'},
skip_stat_lines = i18n.stat_skip_patterns.maps,
},
['Currency'] = {
frame_type = 'currency',
},
['Stackable Currency'] = {
args = {'is_essence', 'essence_level_restriction', 'essence_level'},
frame_type = 'currency',
},
['Microtransactions'] = {
frame_type = 'currency',
},
['Hideout Doodads'] = {
tables = {'hideout_doodads'},
args = {'is_master_doodad', 'master', 'master_level_requirement', 'master_favour_cost', 'variation_count'},
defaults = {
help_text = i18n.help_text_defaults.hideout_doodad,
},
frame_type = 'currency',
},
['Jewel'] = {
late_args = {'item_limit', 'jewel_radius_html'},
defaults = {
help_text = i18n.help_text_defaults.jewel,
},
skip_stat_lines = i18n.stat_skip_patterns.jewels,
},
['Abyss Jewel'] = {
late_args = {'item_limit', 'jewel_radius_html'},
skip_stat_lines = i18n.stat_skip_patterns.jewels,
},
['Quest Items'] = {
args = {'description'},
frame_type = 'quest',
},
['Divination Card'] = {
tables = {'divination_cards'},
args = {'card_art',},
frame_type = 'divicard',
},
['Labyrinth Item'] = {
frame_type = 'currency',
},
['Labyrinth Trinket'] = {
args = {tables_assoc, 'buff_icon'},
frame_type = 'currency',
},
['Pantheon Soul'] = {
defaults = {
cannot_be_traded_or_modified = true,
},
},
}
-- add defaults from class specifics and class groups
core.item_classes = {}
core.item_classes_extend = {'tables', 'args', 'late_args'}
function core.build_item_classes(tpl_args, frame)
core.map.class.func(tpl_args, frame)
-- Skip building for anything but the specified class.
for _, data in ipairs(m_game.constants.item.class) do
if data['full'] == tpl_args.class then
core.item_classes[data['full']] = {
tables = xtable:new(),
args = xtable:new(),
late_args = xtable:new(),
defaults = {},
}
core.item_classes[data['full']].tables:insertT(core.tables)
break
end
end
for _, row in pairs(core.class_groups) do
for class, _ in pairs(row.keys) do
if class == tpl_args.class then
for _, k in ipairs(core.item_classes_extend) do
if row[k] ~= nil then
core.item_classes[class][k]:insertT(row[k])
end
end
break
end
end
end
local class_specifics = core.class_specifics[tpl_args.class]
if class_specifics then
for _, k in ipairs(core.item_classes_extend) do
if class_specifics[k] ~= nil then
core.item_classes[tpl_args.class][k]:insertT(class_specifics[k])
end
end
if class_specifics.defaults ~= nil then
for key, value in pairs(class_specifics.defaults) do
core.item_classes[tpl_args.class].defaults[key] = value
end
end
end
end
-- GroupTable -> RowTable -> formatter function
--
--
--
-- Contents here are meant to resemble the ingame infobox of items
--
core.item_display_groups = {
-- Tags, stats, level, etc
{
{
args = {'cosmetic_type'},
func = core.factory.display_value{
options = {
[1] = {
key = 'cosmetic_type',
fmt = '%s',
color = 'default'
},
},
},
},
{
args = function(tpl_args, frame)
if tpl_args.class == nil then
return false
end
return core.class_groups.weapons.keys[tpl_args.class] ~= nil
end,
func = core.factory.display_value{
options = {
[1] = {
key = 'class',
color = 'default',
},
},
},
},
{
args = {'gem_tags'},
func = function(tpl_args, frame)
local out = {}
for i, tag in ipairs(tpl_args.gem_tags) do
out[#out+1] = string.format(i18n.gem_tag_category, tag, tag)
end
return table.concat(out, ', ')
end,
},
{
args = {'support_gem_letter_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'support_gem_letter_html',
inline = i18n.tooltips.support_icon,
},
},
},
},
{
args = {'radius'},
func = core.factory.display_value{
options = {
[1] = {
key = 'radius',
inline = i18n.tooltips.radius,
func = core.factory.descriptor_value{key='radius_description'},
},
[2] = {
key = 'radius_secondary',
inline = ' / %s',
func = core.factory.descriptor_value{key='radius_secondary_description'},
},
[3] = {
key = 'radius_tertiary',
inline = ' / %s',
func = core.factory.descriptor_value{key='radius_tertiary_description'},
},
},
},
},
-- TODO: gem level here. Maybe put max level here?
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'mana_cost',
hide_default = 100,
fmt = function (tpl_args, frame)
if tpl_args.has_percentage_mana_cost then
return '%i%%'
else
return '%i'
end
end,
inline = function (tpl_args, frame)
if tpl_args.has_reservation_mana_cost then
return i18n.tooltips.mana_reserved
else
return i18n.tooltips.mana_cost
end
end,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'mana_multiplier',
hide_default = 100,
fmt = '%i%%',
inline = i18n.tooltips.mana_multiplier,
},
},
},
},
-- TODO: i18n
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'vaal_souls_requirement',
hide_default = 0,
fmt = '%i (N) / ',
inline = i18n.tooltips.vaal_souls_per_use,
},
[2] = {
key = 'vaal_souls_requirement',
hide_default = 0,
fmt = '%i (C) / ',
func = function (tpl_args, frame, value)
return value*1.5
end,
},
[3] = {
key = 'vaal_souls_requirement',
hide_default = 0,
fmt = '%i (M)',
func = function (tpl_args, frame, value)
return value*2
end,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'vaal_stored_uses',
hide_default = 0,
fmt = '%i',
inline = i18n.tooltips.stored_uses,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'stored_uses',
hide_default = 0,
fmt = '%i',
inline = i18n.tooltips.stored_uses,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'cooldown',
hide_default = 0,
fmt = '%.2f sec',
inline = i18n.tooltips.cooldown_time,
},
},
},
},
{
args = {'cast_time'},
func = core.factory.display_value{
options = {
[1] = {
key = 'cast_time',
hide_default = 0,
fmt = '%.2f sec',
inline = i18n.tooltips.cast_time,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'critical_strike_chance',
hide_default = 0,
fmt = '%.2f%%',
inline = i18n.tooltips.critical_strike_chance,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'damage_effectiveness',
hide_default = 100,
fmt = '%i%%',
inline = i18n.tooltips.damage_effectiveness,
},
},
},
},
{
args = {'projectile_speed'},
func = core.factory.display_value{
options = {
[1] = {
key = 'projectile_speed',
inline = i18n.tooltips.projectile_speed,
},
},
},
},
-- Quality is before item stats, but after gem stuff and item class
{
args = {'quality'},
func = core.factory.display_value{
options = {
[1] = {
key = 'quality',
fmt = '+%i%%',
color = 'mod',
inline = i18n.tooltips.quality,
hide_default = 0,
},
},
},
},
-- Weapon only
{
args = {'physical_damage_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'physical_damage_html',
fmt = '%s',
inline = i18n.tooltips.physical_damage,
},
},
},
},
{
args = nil,
func = function(tpl_args, frame)
local text = ''
for _, dtype in ipairs({'fire_damage_html', 'cold_damage_html', 'lightning_damage_html'}) do
local value = tpl_args[dtype]
if value ~= nil then
text = text .. ' ' .. value
end
end
if text ~= '' then
return string.format(i18n.tooltips.elemental_damage, text)
else
return
end
end,
},
{
args = {'chaos_damage_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'chaos_damage_html',
fmt = '%s',
inline = i18n.tooltips.chaos_damage,
},
},
},
},
{
args = {'critical_strike_chance_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'critical_strike_chance_html',
fmt = '%s',
inline = i18n.tooltips.critical_strike_chance,
},
},
},
},
{
args = {'attack_speed_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'attack_speed_html',
fmt = '%s',
inline = i18n.tooltips.attacks_per_second,
},
},
},
},
{
args = {'range_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'range_html',
fmt = '%s',
inline = i18n.tooltips.weapon_range,
},
},
},
},
-- Map only
{
args = {'map_area_level'},
func = core.factory.display_value{
options = {
[1] = {
key = 'map_area_level',
fmt = '%i',
inline = i18n.tooltips.map_level,
},
},
},
},
{
args = {'map_tier'},
func = core.factory.display_value{
options = {
[1] = {
key = 'map_tier',
fmt = '%i',
inline = i18n.tooltips.map_tier,
},
},
},
},
{
args = function(tpl_args, frame)
return tpl_args.map_guild_character ~= nil and tpl_args.rarity == 'Normal'
end,
func = core.factory.display_value{
options = {
[1] = {
key = 'map_guild_character',
fmt = '%s',
inline = i18n.tooltips.map_guild_character,
},
},
},
},
{
args = function(tpl_args, frame)
return tpl_args.unique_map_guild_character ~= nil and tpl_args.rarity == 'Unique'
end,
func = core.factory.display_value{
options = {
[1] = {
key = 'unique_map_guild_character',
fmt = '%s',
inline = i18n.tooltips.map_guild_character,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type = 'stat',
options = {
[1] = {
key = 'map_item_drop_quantity_+%',
fmt = '+%i%%',
color = 'mod',
inline = i18n.tooltips.item_quantity,
hide_default = 0,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type = 'stat',
options = {
[1] = {
key = 'map_item_drop_rarity_+%',
fmt = '+%i%%',
color = 'mod',
inline = i18n.tooltips.item_rarity,
hide_default = 0,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type = 'stat',
options = {
[1] = {
key = 'map_pack_size_+%',
fmt = '+%i%%',
color = 'mod',
inline = i18n.tooltips.monster_pack_size,
hide_default = 0,
},
},
},
},
-- Jewel Only
{
args = nil,
func = core.factory.display_value{
options = {
[1] = {
key = 'item_limit',
fmt = '%i',
inline = i18n.tooltips.limited_to,
},
},
},
},
{
args = {'jewel_radius_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'jewel_radius_html',
fmt = '%s',
inline = i18n.tooltips.radius,
},
},
},
},
-- Flask only
{
args = {'flask_mana_html', 'flask_duration_html'},
--func = core.factory.display_flask('flask_mana'),
func = core.factory.display_value{
inline = i18n.tooltips.flask_mana_recovery,
options = {
[1] = {
key = 'flask_mana_html',
fmt = '%s',
},
[2] = {
key = 'flask_duration_html',
fmt = '%s',
},
}
},
},
{
args = {'flask_life_html', 'flask_duration_html'},
func = core.factory.display_value{
inline = i18n.tooltips.flask_life_recovery,
options = {
[1] = {
key = 'flask_life_html',
fmt = '%s',
},
[2] = {
key = 'flask_duration_html',
fmt = '%s',
},
}
},
},
{
-- don't display for mana/life flasks
args = function(tpl_args, frame)
for _, k in ipairs({'flask_life_html', 'flask_mana_html'}) do
if tpl_args[k] ~= nil then
return false
end
end
return tpl_args['flask_duration_html'] ~= nil
end,
func = core.factory.display_value{
inline = i18n.tooltips.flask_duration,
options = {
[1] = {
key = 'flask_duration_html',
fmt = '%s',
},
},
},
},
{
args = {'charges_per_use_html', 'charges_max_html'},
func = core.factory.display_value{
inline = i18n.tooltips.flask_charges_per_use,
options = {
[1] = {
key = 'charges_per_use_html',
fmt = '%s',
},
[2] = {
key = 'charges_max_html',
fmt = '%s',
},
},
},
},
{
args = {'buff_stat_text'},
func = core.factory.display_value{
options = {
[1] = {
key = 'buff_stat_text',
color = 'mod',
},
},
},
},
-- armor
{
args = {'block_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'block_html',
inline = i18n.tooltips.chance_to_block,
fmt = '%s',
hide_default = 0,
hide_default_key = 'block',
},
},
},
},
{
args = {'armour_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'armour_html',
inline = i18n.tooltips.armour,
fmt = '%s',
hide_default = 0,
hide_default_key = 'armour',
},
},
},
},
{
args = {'evasion_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'evasion_html',
inline = i18n.tooltips.evasion,
fmt = '%s',
hide_default = 0,
hide_default_key = 'evasion',
},
},
},
},
{
args = {'energy_shield_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'energy_shield_html',
inline = i18n.tooltips.energy_shield,
fmt = '%s',
hide_default = 0,
hide_default_key = 'energy_shield',
},
},
},
},
-- Amulet only
{
args = {'talisman_tier'},
func = core.factory.display_value{
options = {
[1] = {
key = 'talisman_tier',
fmt = '%i',
inline = i18n.tooltips.talisman_tier,
},
},
},
},
-- Misc
{
args = {'stack_size'},
func = core.factory.display_value{
options = {
[1] = {
key = 'stack_size',
hide_default = 1,
fmt = '%i',
inline = i18n.tooltips.stack_size,
},
},
},
},
-- Essence stuff
{
args = {'essence_level'},
func = core.factory.display_value{
options = {
[1] = {
key = 'essence_level',
fmt = '%i',
inline = i18n.tooltips.essence_level,
},
},
},
},
},
-- Requirements
{
-- TODO: i18n Master name?
{
args = {'master', 'master_level_requirement'},
func = function(tpl_args, frame)
-- masters have been validated before
local data
for i, rowdata in ipairs(m_game.constants.masters) do
if tpl_args.master == rowdata.full then
data = rowdata
break
end
end
return m_util.html.poe_color('default', i18n.tooltips.requires, string.format('[[%s|%s %s]]', data.full, data.short_upper, tpl_args.master_level_requirement))
end
},
-- Instead of item level, show drop level if any
{
args = nil,
func = function(tpl_args, frame)
local opt = {
[1] = {
key = 'required_level_final_html',
hide_default = 1,
hide_default_key = 'required_level_final',
inline = i18n.tooltips.level_inline,
inline_color = false,
},
}
for _, attr in ipairs(m_game.constants.attributes) do
opt[#opt+1] = {
key = string.format('required_%s_html', attr['long_lower']),
hide_default = 0,
hide_default_key = string.format('required_%s', attr['long_lower']),
inline = ', %s ' .. attr['short_upper'],
inline_color = false,
}
end
local requirements = core.factory.display_value{options = opt}(tpl_args, frame)
-- return early
if requirements == nil then
return
end
requirements = string.gsub(requirements, '^, ', '')
return m_util.html.poe_color('default', string.format(i18n.tooltips.requires, requirements))
end,
},
},
-- Gem description
{
css_class = '-textwrap tc -gemdesc',
{
args = {'gem_description'},
func = core.factory.display_value_only('gem_description'),
},
},
-- Gem Quality Stats
{
css_class = '-textwrap tc -mod',
{
args = {'quality_stat_text'},
func = function(tpl_args, frame)
lines = {}
lines[#lines+1] = m_util.html.poe_color('default', i18n.tooltips.gem_quality)
lines[#lines+1] = tpl_args.quality_stat_text
return table.concat(lines, '<br>')
end,
},
},
-- Gem Implicit Stats
{
css_class = '-textwrap tc -mod',
{
args = function(tpl_args, frame)
return core.class_groups.gems.keys[tpl_args.class] and tpl_args.stat_text
end,
func = function(tpl_args, frame)
lines = {}
lines[#lines+1] = tpl_args.stat_text
if tpl_args.gem_tags:contains('Vaal') then
lines[#lines+1] = m_util.html.poe_color('corrupted', i18n.tooltips.corrupted)
end
return table.concat(lines, '<br>')
end,
},
},
-- Implicit Stats
{
css_class = '-textwrap tc -mod',
func = function(tpl_args, frame)
if tpl_args.implicit_stat_text ~= '' then
return {tpl_args.implicit_stat_text}
else
return {}
end
end,
},
-- Stats
{
css_class = '-textwrap tc -mod',
func = function(tpl_args, frame)
if tpl_args.explicit_stat_text ~= '' then
return {tpl_args.explicit_stat_text}
else
return {}
end
end,
},
-- Experience
--[[{
{
args = {'experience'},
func = core.factory.display_value{
key = 'experience',
options = {
[1] = {
fmt = '%i',
},
},
},
},
},]]--
-- Description (currency, doodads)
{
css_class = '-textwrap tc -mod',
{
args = {'description'},
func = core.factory.display_value_only('description'),
},
},
-- Variations (for doodads)
{
css_class = 'tc -mod',
{
args = {'variation_count'},
func = function(tpl_args, frame)
local txt
if tpl_args.variation_count == 1 then
txt = i18n.tooltips.variation_singular
else
txt = i18n.tooltips.variation_plural
end
return string.format('%i %s', tpl_args.variation_count, txt)
end,
},
},
-- Flavour Text
{
css_class = '-textwrap tc -flavour',
{
args = {'flavour_text'},
func = core.factory.display_value_only('flavour_text'),
},
},
-- Prophecy text
{
css_class = '-textwrap tc -value',
{
args = {'prediction_text'},
func = core.factory.display_value_only('prediction_text'),
},
},
-- Can not be traded or modified
{
css_class = '-textwrap tc -canttradeormodify',
{
args = {'cannot_be_traded_or_modified'},
func = function(tpl_args, frame)
if tpl_args.cannot_be_traded_or_modified == true then
return i18n.tooltips.cannot_be_traded_or_modified
end
end,
},
},
-- Help text
{
css_class = '-textwrap tc -help',
{
args = {'help_text'},
func = core.factory.display_value_only('help_text'),
},
},
-- Cost (i.e. vendor costs)
{
--css_class = '',
{
args = {'master_favour_cost'},
func = core.factory.display_value{
options = {
[1] = {
key = 'master_favour_cost',
inline = i18n.tooltips.favour_cost,
color = 'currency',
},
},
},
},
{
args = {'seal_cost'},
func = core.factory.display_value{
options = {
[1] = {
key = 'seal_cost',
fmt = '%dx ',
color = 'currency',
inline = function (tpl_args, frame)
return i18n.tooltips.seal_cost .. f_item_link{item_name_exact='Silver Coin', html=''}
end,
},
},
},
},
},
}
--
-- This is meant to show additional information about the item in a separate infobox
--
core.extra_display_groups = {
-- Drop info
{
header = i18n.tooltips.drop_restrictions,
{
args = {'drop_enabled'},
func = core.factory.display_value{
options = {
[1] = {
key = 'drop_level',
fmt = '%i',
inline = i18n.tooltips.level,
},
[2] = {
key = 'drop_level_maximum',
hide_default = 100,
fmt = '%i',
inline = ' / %s',
},
},
},
},
{
args = {'drop_leagues'},
func = function(tpl_args, frame)
return string.format(i18n.tooltips.league_restriction, m_util.html.poe_color('value', table.concat(tpl_args.drop_leagues, ', ')))
end
},
{
args = {'drop_areas_html'},
func = core.factory.display_value_only('drop_areas_html'),
},
{
args = {'drop_text'},
func = core.factory.display_value_only('drop_text'),
},
{
args = function(tpl_args, frame)
if tpl_args.drop_enabled == true then
return false
end
return true
end,
func = function(tpl_args, frame)
local span = mw.html.create('span')
span
:attr('class', 'infobox-disabled-drop')
:wikitext(i18n.tooltips.drop_disabled)
:done()
return tostring(span)
end,
},
},
{
header = i18n.tooltips.purchase_costs,
{
args = function(tpl_args, frame)
for rarity, data in pairs(tpl_args.purchase_costs) do
if #data > 0 then
return true
end
end
return false
end,
func = function(tpl_args, frame)
local tbl = mw.html.create('table')
tbl
--:attr('class', 'wikitable')
:attr('style', 'width: 100%; margin-top: 0px;')
for _, rarity_names in ipairs(m_game.constants.item.rarity) do
local data = tpl_args.purchase_costs[rarity_names.long_lower]
if #data > 0 then
local tr = tbl:tag('tr')
tr
:tag('td')
:wikitext(rarity_names.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,
},
},
{
header = i18n.tooltips.sell_price,
{
args = {'sell_price_order'},
func = function(tpl_args, frame)
local out = {}
for _, item_name in ipairs(tpl_args.sell_price_order) do
out[#out+1] = string.format('%dx [[%s]]', tpl_args.sell_prices[item_name], item_name)
end
return table.concat(out, '<br />')
end,
},
},
-- Damage per second
{
header = i18n.tooltips.damage_per_second,
-- Autoinsert here from dps map
},
}
for i, data in ipairs(core.dps_map) do
table.insert(core.extra_display_groups[4], {
args = {data.name .. '_html'},
func = core.factory.display_value{
options = {
[1] = {
key = data.name .. '_html',
inline = data.label_infobox .. ': %s',
fmt = '%s',
-- the html already contains the colour
no_color = true,
},
},
},
})
if i == 5 then
table.insert(core.extra_display_groups[4], {
args = function (tpl_args, frame)
return tpl_args.elemental_dps_html ~= nil or tpl_args.poison_dps_html ~= nil
end,
func = function (tpl_args, frame)
return ''
end,
})
elseif i == 7 then
table.insert(core.extra_display_groups[4], {
args = {'dps_html'},
func = function (tpl_args, frame)
return ''
end,
})
end
end
core.result = {}
-- for sort type see:
-- https://meta.wikimedia.org/wiki/Help:Sorting
core.result.generic_item = {
{
arg = 'base_item',
header = i18n.item_table.base_item,
fields = {'items.base_item', 'items.base_item_page'},
display = function(tr, data)
tr
:tag('td')
:attr('data-sort-value', data['items.base_item'])
:wikitext(string.format('[[%s|%s]]', data['items.base_item_page'], data['items.base_item']))
end,
order = 1000,
sort_type = 'text',
},
{
arg = 'class',
header = i18n.item_table.item_class,
fields = {'items.class'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 1001,
sort_type = 'text',
},
{
arg = 'essence',
header = i18n.item_table.essence_level,
fields = {'essences.level'},
display = h.tbl.display.factory.value{},
order = 2000,
},
{
arg = {'drop', 'drop_level'},
header = i18n.item_table.drop_level,
fields = {'items.drop_level'},
display = h.tbl.display.factory.value{},
order = 3000,
},
{
arg = 'stack_size',
header = i18n.item_table.stack_size,
fields = {'stackables.stack_size'},
display = h.tbl.display.factory.value{},
order = 4000,
},
{
arg = 'stack_size_currency_tab',
header = i18n.item_table.stack_size_currency_tab,
fields = {'stackables.stack_size_currency_tab'},
display = h.tbl.display.factory.value{},
order = 4001,
},
{
arg = 'level',
header = m_game.level_requirement.icon,
fields = h.tbl.range_fields('items.required_level'),
display = h.tbl.display.factory.range{field='items.required_level'},
order = 5000,
},
{
arg = 'ar',
header = i18n.item_table.armour,
fields = h.tbl.range_fields('armours.armour'),
display = h.tbl.display.factory.range{field='armours.armour'},
order = 6000,
},
{
arg = 'ev',
header =i18n.item_table.evasion,
fields = h.tbl.range_fields('armours.evasion'),
display = h.tbl.display.factory.range{field='armours.evasion'},
order = 6001,
},
{
arg = 'es',
header = i18n.item_table.energy_shield,
fields = h.tbl.range_fields('armours.energy_shield'),
display = h.tbl.display.factory.range{field='armours.energy_shield'},
order = 6002,
},
{
arg = 'block',
header = i18n.item_table.block,
fields = h.tbl.range_fields('shields.block'),
display = h.tbl.display.factory.range{field='shields.block'},
order = 6003,
},
--[[{
arg = 'physical_damage_min',
header = m_util.html.abbr('Min', 'Local minimum weapon damage'),
fields = h.tbl.range_fields('minimum physical damage'),
display = h.tbl.display.factory.range{field='minimum physical damage'},
order = 7000,
},
{
arg = 'physical_damage_max',
header = m_util.html.abbr('Max', 'Local maximum weapon damage'),
fields = h.tbl.range_fields('maximum physical damage'),
display = h.tbl.display.factory.range{field='maximum physical damage'},
order = 7001,
},]]--
{
arg = {'weapon', 'damage'},
header = i18n.item_table.damage,
fields = {'weapons.damage_html', 'weapons.damage_avg'},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['weapons.damage_avg'])
:wikitext(data['weapons.damage_html'])
end,
order = 8000,
},
{
arg = {'weapon', 'aps'},
header = i18n.item_table.attacks_per_second,
fields = h.tbl.range_fields('weapons.attack_speed'),
display = h.tbl.display.factory.range{field='weapons.attack_speed'},
order = 8001,
},
{
arg = {'weapon', 'crit'},
header = i18n.item_table.local_critical_strike_chance,
fields = h.tbl.range_fields('weapons.critical_strike_chance'),
display = h.tbl.display.factory.range{field='weapons.critical_strike_chance'},
order = 8002,
},
{
arg = 'flask_life',
header = i18n.item_table.flask_life,
fields = h.tbl.range_fields('flasks.life'),
display = h.tbl.display.factory.range{field='flasks.life'},
order = 9000,
},
{
arg = 'flask_mana',
header = i18n.item_table.flask_mana,
fields = h.tbl.range_fields('flasks.mana'),
display = h.tbl.display.factory.range{field='flasks.mana'},
order = 9001,
},
{
arg = 'flask',
header = i18n.item_table.flask_duration,
fields = h.tbl.range_fields('flasks.duration'),
display = h.tbl.display.factory.range{field='flasks.duration'},
order = 9002,
},
{
arg = 'flask',
header = i18n.item_table.flask_charges_per_use,
fields = h.tbl.range_fields('flasks.charges_per_use'),
display = h.tbl.display.factory.range{field='flasks.charges_per_use'},
order = 9003,
},
{
arg = 'flask',
header = i18n.item_table.flask_maximum_charges,
fields = h.tbl.range_fields('flasks.charges_max'),
display = h.tbl.display.factory.range{field='flasks.charges_max'},
order = 9004,
},
{
arg = 'item_limit',
header = i18n.item_table.item_limit,
fields = {'jewels.item_limit'},
display = h.tbl.display.factory.value{},
order = 10000,
},
{
arg = 'jewel_radius',
header = i18n.item_table.jewel_radius,
fields = {'jewels.radius_html'},
display = function (tr, data)
tr
:tag('td')
:wikitext(data['jewels.radius_html'])
end,
order = 10001,
},
{
arg = 'map_tier',
header = i18n.item_table.map_tier,
fields = {'maps.tier'},
display = h.tbl.display.factory.value{},
order = 11000,
},
{
arg = 'map_level',
header = i18n.item_table.map_level,
fields = {'maps.area_level'},
display = h.tbl.display.factory.value{},
order = 11010,
},
{
arg = 'map_guild_character',
header = i18n.item_table.map_guild_character,
fields = {'maps.guild_character'},
display = h.tbl.display.factory.value{colour='value'},
order = 11020,
sort_type = 'text',
},
{
arg = 'buff',
header = i18n.item_table.buff_effects,
fields = {'item_buffs.stat_text'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12000,
sort_type = 'text',
},
{
arg = 'stat',
header = i18n.item_table.stats,
fields = {'items.stat_text'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12001,
sort_type = 'text',
},
{
arg = 'description',
header = i18n.item_table.effects,
fields = {'items.description'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12002,
sort_type = 'text',
},
{
arg = 'flavour_text',
header = i18n.item_table.flavour_text,
fields = {'items.flavour_text'},
display = h.tbl.display.factory.value{colour='flavour'},
order = 12003,
sort_type = 'text',
},
{
arg = 'help_text',
header = i18n.item_table.help_text,
fields = {'items.help_text'},
display = h.tbl.display.factory.value{colour='help'},
order = 12005,
sort_type = 'text',
},
{
arg = {'prophecy', 'objective'},
header = i18n.item_table.objective,
fields = {'prophecies.objective'},
display = h.tbl.display.factory.value{},
order = 13002,
},
{
arg = {'prophecy', 'reward'},
header = i18n.item_table.reward,
fields = {'prophecies.reward'},
display = h.tbl.display.factory.value{},
order = 13001,
},
{
arg = {'prophecy', 'seal_cost'},
header = i18n.item_table.seal_cost,
fields = {'prophecies.seal_cost'},
display = h.tbl.display.factory.value{colour='currency'},
order = 13002,
},
{
arg = {'prediction_text'},
header = i18n.item_table.prediction_text,
fields = {'prophecies.prediction_text'},
display = h.tbl.display.factory.value{colour='value'},
order = 12004,
sort_type = 'text',
},
{
arg = 'buff_icon',
header = i18n.item_table.buff_icon,
fields = {'item_buffs.icon'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 14000,
sort_type = 'text',
},
{
arg = {'drop', 'drop_leagues'},
header = i18n.item_table.drop_leagues,
fields = {'items.drop_leagues'},
display = function (tr, data)
tr
:tag('td')
:wikitext(table.concat(m_util.string.split(data['items.drop_leagues'], ','), '<br>'))
end,
order = 15000,
},
{
arg = {'drop', 'drop_areas'},
header = i18n.item_table.drop_areas,
fields = {'items.drop_areas_html'},
display = h.tbl.display.factory.value{},
order = 15001,
},
{
arg = {'drop', 'drop_text'},
header = i18n.item_table.drop_text,
fields = {'items.drop_text'},
display = h.tbl.display.factory.value{},
order = 15002,
},
}
for i, data in ipairs(core.dps_map) do
table.insert(core.result.generic_item, {
arg = data.name,
header = data.label,
fields = h.tbl.range_fields(string.format('weapons.%s', data.field)),
display = h.tbl.display.factory.range{field=string.format('weapons.%s', data.field)},
order = 8100+i,
})
end
core.result.skill_gem_new = {
{
arg = 'icon',
header = i18n.item_table.support_gem_letter,
fields = {'skill_gems.support_gem_letter_html'},
display = h.tbl.display.factory.value{},
order = 1000,
sort_type = 'text',
},
{
arg = 'skill_icon',
header = i18n.item_table.skill_icon,
fields = {'skills3.skill_icon'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 1001,
sort_type = 'text',
},
{
arg = 'description',
header = i18n.item_table.description,
fields = {'skills3.description'},
display = h.tbl.display.factory.value{},
order = 2000,
sort_type = 'text',
},
{
arg = 'level',
header = m_game.level_requirement.icon,
fields = {'items.level_requirement'},
display = h.tbl.display.factory.value{},
order = 3004,
},
{
arg = 'crit',
header = i18n.item_table.skill_critical_strike_chance,
fields = {'skill_levels.critical_strike_chance'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
},
}},
order = 4000,
},
{
arg = 'cast_time',
header = i18n.item_table.cast_time,
fields = {'skill_levels.cast_time'},
display = h.tbl.display.factory.value{},
order = 4001,
},
{
arg = 'dmgeff',
header = i18n.item_table.damage_effectiveness,
fields = {'skill_levels.damage_effectiveness'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
},
}},
order = 4002,
},
{
arg = 'mcm',
header = i18n.item_table.mana_cost_multiplier,
fields = {'skill_levels.mana_multiplier'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
},
}},
order = 5000,
},
{
arg = 'mana',
header = i18n.item_table.mana_cost,
fields = {'skill_levels.mana_cost', 'skills3.has_percentage_mana_cost', 'skills3.has_reservation_mana_cost'},
display = function (tr, data)
local appendix = ''
if m_util.cast.boolean(data['skills3.has_percentage_mana_cost']) then
appendix = appendix .. '%'
end
if m_util.cast.boolean(data['skills3.has_reservation_mana_cost']) then
appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
end
tr
:tag('td')
:attr('data-sort-value', data['skill_levels.mana_cost'])
:wikitext(string.format('%d', data['skill_levels.mana_cost']) .. appendix)
end,
order = 5001,
},
{
arg = 'vaal',
header = i18n.item_table.vaal_souls_requirement,
fields = {'skill_levels.vaal_souls_requirement'},
display = function (tr, data)
local souls = tonumber(data['skill_levels.vaal_souls_requirement'])
tr
:tag('td')
:attr('data-sort-value', souls)
:wikitext(string.format('%d / %d / %d', souls, souls*1.5, souls*2))
end,
order = 6000,
},
{
arg = 'vaal',
header = i18n.item_table.stored_uses,
fields = {'skill_levels.vaal_stored_uses'},
display = h.tbl.display.factory.value{},
order = 6001,
},
{
arg = 'radius',
header = i18n.item_table.primary_radius,
fields = {'skills3.radius', 'skills3.radius_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skills3.radius'])
:wikitext(core.factory.descriptor_value{tbl=data, key='skills3.radius_description'}(nil, nil, data['skills3.radius']))
end,
order = 7000,
},
{
arg = 'radius',
header = i18n.item_table.secondary_radius,
fields = {'skills3.radius_secondary', 'skills3.radius_secondary_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skills3.radius_secondary'])
:wikitext(core.factory.descriptor_value{tbl=data, key='skills3.radius_secondary_description'}(nil, nil, data['skills3.radius_secondary']))
end,
order = 7001,
},
{
arg = 'radius',
header = i18n.item_table.tertiary_radius,
fields = {'skills3.radius_tertiary', 'skills3.radius_tertiary_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skills3.radius_tertiary'])
:wikitext(core.factory.descriptor_value{tbl=data, key='skills3.radius_tertiary_description'}(nil, nil, data['skills3.radius_tertiary']))
end,
order = 7002,
},
}
for i, attr in ipairs(m_game.constants.attributes) do
table.insert(core.result.generic_item, 7, {
arg = attr.short_lower,
header = attr.icon,
fields = h.tbl.range_fields(string.format('items.required_%s', attr.long_lower)),
display = h.tbl.display.factory.range{field=string.format('items.required_%s', attr.long_lower)},
order = 5000+i,
})
table.insert(core.result.skill_gem_new, 1, {
arg = attr.short_lower,
header = attr.icon,
fields = {string.format('skill_gems.%s_percent', attr.long_lower)},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data[string.format('skill_gems.%s_percent', attr.long_lower)])
:wikitext('[[File:Yes.png|yes|link=]]')
end,
order = 3000+i,
})
end
-- ----------------------------------------------------------------------------
-- Tables
-- ----------------------------------------------------------------------------
function cargo_declare(data)
return function(frame)
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
local dcl_args = {}
dcl_args._table = data.table
for k, field_data in pairs(data.fields) do
if field_data.field then
dcl_args[field_data.field] = field_data.type
for _, stat_data in pairs(core.stat_map) do
if stat_data.field == k then
for _, range_fields in ipairs(h.range_fields) do
dcl_args[stat_data.field .. range_fields.field] = range_fields.type
end
break
end
end
end
end
if dcl_args._table == 'weapons' then
for _, dps_data in ipairs(core.dps_map) do
for _, range_fields in ipairs(h.range_fields) do
dcl_args[dps_data.field .. range_fields.field] = range_fields.type
end
end
end
if tpl_args.debug then
mw.logObject(dcl_args)
end
return m_util.cargo.declare(frame, dcl_args)
end
end
p.table_items = cargo_declare(core.cargo.items)
p.table_item_purchase_costs = cargo_declare(core.cargo.item_purchase_costs)
p.table_item_stats = cargo_declare(core.cargo.item_stats)
p.table_item_buffs = cargo_declare(core.cargo.item_buffs)
p.table_upgraded_from_sets = cargo_declare(core.cargo.upgraded_from_sets)
p.table_upgraded_from_groups = cargo_declare(core.cargo.upgraded_from_groups)
p.table_amulets = cargo_declare(core.cargo.amulets)
p.table_flasks = cargo_declare(core.cargo.flasks)
p.table_weapons = cargo_declare(core.cargo.weapons)
p.table_armours = cargo_declare(core.cargo.armours)
p.table_shields = cargo_declare(core.cargo.shields)
p.table_skill_gems = cargo_declare(core.cargo.skill_gems)
p.table_maps = cargo_declare(core.cargo.maps)
p.table_stackables = cargo_declare(core.cargo.stackables)
p.table_essences = cargo_declare(core.cargo.essences)
p.table_hideout_doodads = cargo_declare(core.cargo.hideout_doodads)
p.table_prophecies = cargo_declare(core.cargo.prophecies)
p.table_divination_cards = cargo_declare(core.cargo.divination_cards)
p.table_jewels = cargo_declare(core.cargo.jewels)
-- ----------------------------------------------------------------------------
-- Page views
-- ----------------------------------------------------------------------------
--
-- Template:Item
--
function p.itembox (frame)
--
-- Args/Frame
--
local t = os.clock()
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
--
-- Shared args
--
tpl_args._flags = {}
tpl_args._base_item_args = {}
tpl_args._mods = {}
tpl_args._stats = {}
tpl_args._implicit_stats = {}
tpl_args._explicit_stats = {}
tpl_args._subobjects = {}
tpl_args._properties = {}
tpl_args._errors = {}
core.build_item_classes(tpl_args, frame)
core.build_cargo_data(tpl_args, frame)
-- Using general purpose function to handle release and removal versions
m_util.args.version(tpl_args, {frame=frame, set_properties=true})
-- Must validate some argument early. It is required for future things
core.process_arguments(tpl_args, frame, {array=core.default_args})
core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].args})
-- Base Item
core.process_base_item(tpl_args, frame)
-- Prophecy special snowflake
if tpl_args.base_item == 'Prophecy' then
err = core.process_arguments(tpl_args, frame, {array=core.prophecy_args})
if err then
return err
end
tpl_args.inventory_icon = string.format(i18n.inventory_icon, 'Prophecy')
end
-- Mods
for _, k in ipairs({'implicit', 'explicit'}) do
local success = true
local i = 1
while success do
success = core.validate_mod(tpl_args, frame, {key=k, i=i})
i = i + 1
end
end
core.process_smw_mods(tpl_args, frame)
-- Add stats - this is for when mods are not set, but we still need stats to calcuate new armour values etc
m_util.args.stats(tpl_args, {prefix='extra_'})
for _, stat in ipairs(tpl_args.extra_stats) do
if stat.value ~= nil then
stat.min = stat.value
stat.max = stat.value
stat.avg = stat.value
end
h.stats_update(tpl_args, stat.id, stat, nil, '_stats')
h.stats_update(tpl_args, stat.id, stat, nil, '_explicit_stats')
end
-- Transpose stats into cargo data
for id, data in pairs(tpl_args._stats) do
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_stats',
id = id,
min = data.min,
max = data.max,
avg = data.avg,
is_implicit = nil,
}
end
for id, data in pairs(tpl_args._explicit_stats) do
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_stats',
id = id,
min = data.min,
max = data.max,
avg = data.avg,
is_implicit = false,
}
end
for id, data in pairs(tpl_args._implicit_stats) do
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_stats',
id = id,
min = data.min,
max = data.max,
avg = data.avg,
is_implicit = true,
}
end
-- Handle extra stats (for gems)
if core.class_groups.gems.keys[tpl_args.class] then
m_skill.skill(frame, tpl_args)
end
--
-- Handle local stats increases/reductions/additions
--
local skip = {}
-- general stats
for k, data in pairs(core.stat_map) do
local value = tpl_args[k]
if value ~= nil and skip[k] == nil then
value = {min=value, max=value, base=value}
-- If stats are overriden we scan save some CPU time here
local overridden = false
if data.stats_override ~= nil then
for stat_id, override_value in pairs(data.stats_override) do
local stat_value = tpl_args._stats[stat_id]
if stat_value ~= nil then
-- Use the value of stat
if override_value == true then
value.min = stat_value.min
value.max = stat_value.max
overridden = true
elseif stat_value ~= 0 then
value.min = override_value.min
value.max = override_value.max
overridden = true
end
end
end
end
if overridden == false then
-- The simple cases; this must be using ipairs as "add" must apply before
for _, operator in ipairs({'add', 'more'}) do
local st = data['stats_' .. operator]
if st ~= nil then
for _, statid in ipairs(st) do
if tpl_args._stats[statid] ~= nil then
h.stat[operator](value, tpl_args._stats[statid])
end
end
end
end
-- For increased stats we need to add them up first
for stat_key, stat_func in pairs({increased=h.stat.more, increased_inverse=h.stat.more_inverse}) do
local st = data['stats_' .. stat_key]
if st ~= nil then
local total_increase = {min=0, max=0}
for _, statid in ipairs(st) do
if tpl_args._stats[statid] ~= nil then
for var, current_value in pairs(total_increase) do
total_increase[var] = current_value + tpl_args._stats[statid][var]
end
end
end
stat_func(value, total_increase)
end
end
if data.minimum ~= nil then
for _, key in ipairs({'min', 'max'}) do
if value[key] < data.minimum then
value[key] = data.minimum
end
end
end
else
end
value.avg = (value.min + value.max) / 2
-- don't add the properties unless we need to
if (data.default ~= nil and (value.min ~= data.default or value.max ~= data.default)) or data.default == nil then
for short_key, range_data in pairs(h.range_map) do
tpl_args[data.field .. range_data.var] = value[short_key]
end
-- process to HTML to use on list pages or other purposes
h.handle_range_args(tpl_args, frame, k, data.field, value, data.html_fmt_options or {})
end
for short_key, range_data in pairs(h.range_map) do
tpl_args[k .. range_data.var] = value[short_key]
end
end
end
-- calculate and handle weapon dps
if core.class_groups.weapons.keys[tpl_args.class] then
for _, data in ipairs(core.dps_map) do
local damage = {
min = {},
max = {},
}
for var_type, value in pairs(damage) do
-- covers the min/max/avg range
for short_key, range_data in pairs(h.range_map) do
value[short_key] = 0
for _, damage_key in ipairs(data.damage_args) do
value[short_key] = value[short_key] + (tpl_args[string.format('%s_%s%s', damage_key, var_type, range_data.var)] or 0)
end
end
end
local value = {}
for short_key, range_data in pairs(h.range_map) do
local result = (damage.min[short_key] + damage.max[short_key]) / 2 * tpl_args[string.format('attack_speed%s', range_data.var)]
value[short_key] = result
tpl_args[string.format('%s%s', data.field, range_data.var)] = result
end
if value.avg > 0 then
h.handle_range_args(tpl_args, frame, data.name, data.field, value, data.html_fmt_options or {})
end
end
end
-- late processing
core.process_arguments(tpl_args, frame, {array=core.late_args})
core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].late_args})
-- Handle upgrade from restrictions/info
core.process_upgraded_from(tpl_args, frame)
-- ------------------------------------------------------------------------
-- Infobox handling
-- ------------------------------------------------------------------------
local extra_class = ''
local container = mw.html.create('span')
:attr( 'class', 'item-box -' .. tpl_args.frame_type)
if tpl_args.class == 'Divination Card' then
container
:tag('span')
:attr( 'class', 'divicard-wrapper')
:tag('span')
:attr('class', 'divicard-art')
:wikitext( '[[' .. tpl_args.card_art .. '|link=|alt=]]' )
:done()
:tag('span')
:attr('class', 'divicard-frame')
:wikitext( '[[File:Divination card frame.png|link=|alt=]]' )
:done()
:tag('span')
:attr('class', 'divicard-header')
:wikitext(tpl_args.name)
:done()
:tag('span')
:attr('class', 'divicard-stack')
:wikitext(tpl_args.stack_size)
:done()
:tag('span')
:attr('class', 'divicard-reward')
:tag('span')
:wikitext(tpl_args.description)
:done()
:done()
:tag('span')
:attr('class', 'divicard-flavour text-color -flavour')
:tag('span')
:wikitext(tpl_args.flavour_text)
:done()
:done()
:done()
--TODO Extras?
else
local header_css
if tpl_args.base_item and tpl_args.rarity ~= 'Normal' then
line_type = 'double'
else
line_type = 'single'
end
local name_line = tpl_args.name
if tpl_args.base_item and tpl_args.base_item ~= 'Prophecy' then
name_line = name_line .. '<br>' .. tpl_args.base_item
end
container
:tag('span')
:attr( 'class', 'header -' .. line_type )
:wikitext( name_line )
:done()
core.display.add_to_container_from_map(tpl_args, frame, container, core.item_display_groups)
end
if tpl_args.skill_icon ~= nil then
container:wikitext(string.format('[[%s]]', tpl_args.skill_icon))
end
-- Store the infobox so it can be accessed with ease on other pages
tpl_args.html = tostring(container)
if tpl_args.inventory_icon ~= nil and tpl_args.class ~= 'Divination Card' then
container:wikitext(string.format('[[%s|%sx%spx]]', tpl_args.inventory_icon, c.image_size_full*tpl_args.size_x, c.image_size_full*tpl_args.size_y))
end
--
-- Secondary infobox
--
local extra_infobox = mw.html.create('span')
:attr( 'class', 'item-box -' .. tpl_args.frame_type)
core.display.add_to_container_from_map(tpl_args, frame, extra_infobox, core.extra_display_groups)
--
-- Output
--
local infobox = mw.html.create('span')
infobox
:attr('class', 'infobox-page-container')
:node(container)
:node(extra_infobox)
if tpl_args.skill_screenshot then
infobox:wikitext(string.format('<br>[[%s|300px]]', tpl_args.skill_screenshot))
end
local out = tostring(infobox)
-- ------------------------------------------------------------------------
-- Category handling
-- ------------------------------------------------------------------------
local cats = {}
if tpl_args.rarity == 'Unique' then
cats[#cats+1] = string.format(i18n.categories.unique_affix, tpl_args.class)
elseif tpl_args.base_item == 'Prophecy' then
cats[#cats+1] = i18n.categories.prophecies
elseif tpl_args.is_talisman then
cats[#cats+1] = i18n.categories.talismans
elseif tpl_args.is_essence then
cats[#cats+1] = i18n.categories.essences
else
cats[#cats+1] = tpl_args.class
end
for _, attr in ipairs(m_game.constants.attributes) do
if tpl_args[attr.long_lower .. '_percent'] then
cats[#cats+1] = string.format('%s %s', attr.long_upper, tpl_args.class)
end
end
local affix
if tpl_args.class == 'Active Skill Gems' or tpl_args.class == 'Support Skill Gems' then
affix = i18n.categories.gem_tag_affix
end
if affix ~= nil then
for _, tag in ipairs(tpl_args.gem_tags) do
cats[#cats+1] = string.format(affix, tag)
end
end
if #tpl_args.alternate_art_inventory_icons > 0 then
cats[#cats+1] = i18n.categories.alternate_artwork
end
-- TODO: add maintenance categories
if tpl_args.release_version == nil then
cats[#cats+1] = i18n.categories.missing_release_version
end
if tpl_args._flags.text_modifier then
cats[#cats+1] = i18n.categories.improper_modifiers
end
if tpl_args._flags.broken_upgraded_from_reference then
cats[#cats+1] = i18n.categories.broken_upgraded_from_reference
end
out = out .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
--
-- Misc
--
-- Also show the infobox for areas right away for maps, since they're both on the same page
local query_id
if tpl_args.rarity == 'Normal' and tpl_args.map_area_id ~= nil then
query_id = tpl_args.map_area_id
elseif tpl_args.rarity == 'Unique' and unique_map_area_id ~= nil then
local query_id = tpl_args.unique_map_area_id
end
if query_id then
out = out .. m_area.query_area_info{cats=yes, where=string.format('areas.id="%s"', query_id)}
end
-- ------------------------------------------------------------------------
-- Store cargo data
-- ------------------------------------------------------------------------
-- Map argument values for cargo storage
for _, table_name in ipairs(core.item_classes[tpl_args.class].tables) do
tpl_args._subobjects[table_name] = {
_table = table_name,
}
end
for k, v in pairs(tpl_args) do
local data = core.map[k]
if data ~= nil then
if data.table ~= nil and data.field ~= nil then
tpl_args._subobjects[data.table][data.field] = v
elseif data.table == nil and data.field ~= nil then
error(string.format('Missing table for field "%s", key "%s", value "%s", data:\n%s', data.field, k, v, mw.dumpObject(data)))
elseif data.table ~= nil and data.field == nil then
error(string.format('Missing field for table "%s", key "%s", value "%s", data:\n%s', data.table, k, v, mw.dumpObject(data)))
end
end
end
for _, data in pairs(tpl_args._subobjects) do
m_util.cargo.store(frame, data)
end
mw.logObject(os.clock() - t)
-- Show additional error messages in console to help fixing them
mw.logObject(table.concat(tpl_args._errors, '\n'))
return out
end
-- ----------------------------------------------------------------------------
-- Result formatting templates for SMW queries
-- ----------------------------------------------------------------------------
--
-- Template:
--
function p.simple_item_list(frame)
-- Args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
local query = {}
for key, value in pairs(tpl_args) do
if string.sub(key, 0, 2) == 'q_' then
query[string.sub(key, 3)] = value
end
end
local fields = {
'items._pageName',
'items.name',
}
if tpl_args.no_icon == nil then
fields[#fields+1] = 'items.inventory_icon'
end
if tpl_args.no_html == nil then
fields[#fields+1] = 'items.html'
end
local results = m_util.cargo.query(
{'items'},
fields,
query
)
local out = {}
for _, row in ipairs(results) do
local link = f_item_link{page=tpl_args['items._pageName'], name=tpl_args['items.name'], inventory_icon=tpl_args['items.inventory_icon'] or '', html=tpl_args['items.html'] or '', skip_query=true}
if args.format == nil then
out[#out+1] = string.format('* %s', link)
elseif args.format == 'none' then
out[#out+1] = link
elseif args.format == 'li' then
out[#out+1] = string.format('<li>%s</li>', link)
else
error(string.format(i18n.errors.generic_argument_parameter, 'format', args.format))
end
end
if args.format == nil then
return table.concat(out, '\n')
elseif args.format == 'none' then
return table.concat(out, '\n')
elseif args.format == 'li' then
return table.concat(out)
end
end
-- ----------------------------------------------------------------------------
-- Reponsibile for subtemplates of Template:SMW item table
--
function p.item_table(frame)
-- args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
local modes = {
skill = {
data = core.result.skill_gem_new,
header = i18n.item_table.skill_gem,
},
item = {
data = core.result.generic_item,
header = i18n.item_table.item,
},
}
if tpl_args.mode == nil then
tpl_args.mode = 'item'
end
if modes[tpl_args.mode] == nil then
error(i18n.errors.invalid_item_table_mode)
end
local row_infos = {}
for _, row_info in ipairs(modes[tpl_args.mode].data) do
local enabled = false
if row_info.arg == nil then
enabled = true
elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then
enabled = true
elseif type(row_info.arg) == 'table' then
for _, argument in ipairs(row_info.arg) do
if m_util.cast.boolean(tpl_args[argument]) then
enabled = true
break
end
end
end
if enabled then
row_info.options = row_info.options or {}
row_infos[#row_infos+1] = row_info
end
end
-- Parse stat arguments
local stat_columns = {}
local query_stats = {}
local stat_results = {}
local i = 0
repeat
i = i + 1
local prefix = string.format('stat_column%s_', i)
local col_info = {
header = tpl_args[prefix .. 'header'] or tostring(i),
format = tpl_args[prefix .. 'format'],
stat_format = tpl_args[prefix .. 'stat_format'] or 'separate',
order = tonumber(tpl_args[prefix .. 'order']) or (10000000 + i),
stats = {},
options = {},
}
local j = 0
repeat
j = j +1
local stat_info = {
id = tpl_args[string.format('%sstat%s_id', prefix, j)],
}
if stat_info.id then
col_info.stats[#col_info.stats+1] = stat_info
query_stats[stat_info.id] = {}
else
-- Stop iteration entirely if this was the first index but no stat was supplied. We assume that we stop in this case.
if j == 1 then
i = nil
end
-- stop iteration
j = nil
end
until j == nil
-- Don't add this column if no stats were provided.
if #col_info.stats > 0 then
stat_columns[#stat_columns+1] = col_info
end
until i == nil
for _, col_info in ipairs(stat_columns) do
local row_info = {
--arg
header = col_info.header,
fields = {},
display = function(tr, data, properties)
if col_info.stat_format == 'separate' then
local stat_texts = {}
local num_stats = 0
local vmax = 0
for _, stat_info in ipairs(col_info.stats) do
num_stats = num_stats + 1
-- stat results from outside body
local stat = (stat_results[data['items._pageName']] or {})[stat_info.id]
if stat ~= nil then
stat_texts[#stat_texts+1] = h.format_value(tpl_args, frame, stat, {no_color=true})
vmax = vmax + stat.max
end
end
if num_stats ~= #stat_texts then
tr:wikitext(m_util.html.td.na())
else
local text
if col_info.format then
text = string.format(col_info.format, unpack(stat_texts))
else
text = table.concat(stat_texts, ', ')
end
tr:tag('td')
:attr('data-sort-value', vmax)
:attr('class', 'tc -mod')
:wikitext(text)
end
elseif col_info.stat_format == 'add' then
local total_stat = {
min = 0,
max = 0,
avg = 0,
}
for _, stat_info in ipairs(col_info.stats) do
local stat = (stat_results[data['items._pageName']] or {})[stat_info.id]
if stat ~= nil then
for k, v in pairs(total_stat) do
total_stat[k] = v + stat[k]
end
end
end
if col_info.format == nil then
col_info.format = '%s'
end
tr:tag('td')
:attr('data-sort-value', total_stat.max)
:attr('class', 'tc -mod')
:wikitext(string.format(col_info.format, h.format_value(tpl_args, frame, total_stat, {no_color=true})))
else
error(string.format(i18n.errors.generic_argument_parameter, 'stat_format', col_info.stat_format))
end
end,
order = col_info.order,
}
table.insert(row_infos, row_info)
end
-- sort the rows
table.sort(row_infos, function (a, b)
return (a.order or 0) < (b.order or 0)
end)
-- Parse query arguments
local tables_assoc = {items=true}
local fields = {
'items._pageName',
'items.name',
'items.inventory_icon',
'items.html',
'items.size_x',
'items.size_y',
}
--
local prepend = {
q_join=true,
q_groupBy=true,
q_tables=true
}
local query = {}
for key, value in pairs(tpl_args) do
if string.sub(key, 0, 2) == 'q_' then
if prepend[key] then
value = ',' .. value
end
query[string.sub(key, 3)] = value
end
end
--query.limit = 5000
for _, rowinfo in ipairs(row_infos) do
if type(rowinfo.fields) == 'function' then
rowinfo.fields = rowinfo.fields()
end
for index, field in ipairs(rowinfo.fields) do
rowinfo.options[index] = rowinfo.options[index] or {}
fields[#fields+1] = field
tables_assoc[m_util.string.split(field, '%.')[1]] = true
end
end
-- reformat the fields & tables so they can be retrieved correctly
for index, field in ipairs(fields) do
fields[index] = string.format('%s=%s', field, field)
end
local tables = {}
for table_name,_ in pairs(tables_assoc) do
tables[#tables+1] = table_name
end
-- take care of required joins according to the tables
local joins = {}
for index, table_name in ipairs(tables) do
if table_name ~= 'items' then
joins[#joins+1] = string.format('items._pageID=%s._pageID', table_name)
end
end
query.join = table.concat(joins, ',') .. (query.join or '')
-- Cargo workaround: avoid duplicates using groupBy
query.groupBy = 'items._pageID' .. (query.groupBy or '')
local results = cargo.query(
table.concat(tables,',') .. (tpl_args.q_tables or ''),
table.concat(fields,','),
query
)
if #results == 0 and tpl_args.default ~= nil then
return tpl_args.default
end
if #stat_columns > 0 then
local continue = true
local offset = 0
while continue do
if tpl_args.q_where then
tpl_args.q_where = string.format(' AND (%s)', tpl_args.q_where)
else
tpl_args.q_where = ''
end
local temp = cargo.query(
'items,item_stats' .. (tpl_args.q_tables or ''),
'item_stats._pageName=item_stats._pageName, item_stats.id=item_stats.id, item_stats.min=item_stats.min, item_stats.avg=item_stats.avg, item_stats.max=item_stats.max',
{
where='item_stats.is_implicit IS NULL' .. tpl_args.q_where,
join='items._pageID=item_stats._pageID' .. (tpl_args.q_join or ''),
limit=5000,
--offset = offset,
}
)
for _, row in ipairs(temp) do
if query_stats[row['item_stats.id']] ~= nil then
local stat = {
min = tonumber(row['item_stats.min']),
max = tonumber(row['item_stats.max']),
avg = tonumber(row['item_stats.avg']),
}
if stat_results[row['item_stats._pageName']] == nil then
stat_results[row['item_stats._pageName']] = {[row['item_stats.id']] = stat}
else
stat_results[row['item_stats._pageName']][row['item_stats.id']] = stat
end
end
end
-- Cargo doesnt support offset yet
if #temp == 5000 then
--TODO: Cargo
error('Stats > 5000')
--offset = offset + 5000
else
continue = false
end
end
end
local tbl = mw.html.create('table')
tbl:attr('class', 'wikitable sortable item-table')
-- Header
local tr = tbl:tag('tr')
tr
:tag('th')
:wikitext(modes[tpl_args.mode].header)
:done()
for _, row_info in ipairs(row_infos) do
tr
:tag('th')
:attr('data-sort-type', row_info.sort_type or 'number')
:wikitext(row_info.header)
:done()
end
for _, row in ipairs(results) do
tr = tbl:tag('tr')
local il_args = {
skip_query=true,
page=row['items._pageName'],
name=row['items.name'],
inventory_icon=row['items.inventory_icon'],
html=row['items.html'],
width=row['items.size_x'],
height=row['items.size_y'],
}
if tpl_args.large then
il_args.large = tpl_args.large
end
tr
:tag('td')
:wikitext(f_item_link(il_args))
:done()
for _, rowinfo in ipairs(row_infos) do
-- this has been cast from a function in an earlier step
local display = true
for index, field in ipairs(rowinfo.fields) do
-- this will bet set to an empty value not nil confusingly
if row[field] == '' then
if rowinfo.options[index].optional ~= true then
display = false
break
else
row[field] = nil
end
end
end
if display then
rowinfo.display(tr, row, rowinfo.fields)
else
tr:wikitext(m_util.html.td.na())
end
end
end
return tostring(tbl)
end
item_table_factory = {}
function item_table_factory.intro(args)
-- args:
-- data_array
-- header
return function (frame)
-- Args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
--
tpl_args.userparam = m_util.string.split_args(tpl_args.userparam, {sep=', '})
local tr = mw.html.create('tr')
tr
:tag('th')
:wikitext(args.header)
:done()
for _, rowinfo in ipairs(args.data_array) do
if rowinfo.arg == nil or tpl_args.userparam[rowinfo.arg] then
tr
:tag('th')
:wikitext(rowinfo.header)
:done()
end
end
return '<table class="wikitable sortable item-table">' .. tostring(tr)
end
end
-- ----------------------------------------------------------------------------
-- Item lists
-- ----------------------------------------------------------------------------
function p.skill_gem_list_by_gem_tag(frame)
-- Args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
if tpl_args.class == 'Support Skill Gems' then
elseif tpl_args.class == 'Active Skill Gems' then
else
error(i18n.errors.invalid_item_class)
end
local query = {}
query[#query+1] = string.format('[[Has item class::%s]]', tpl_args.class)
query[#query+1] = '?Has gem tags'
query[#query+1] = '?Has name'
query[#query+1] = '?Has inventory icon'
--query[#query+1] = '?Has infobox HTML'
query.limit = 5000
query.sort = 'Has name'
local results = m_util.smw.query(query, frame)
local tags = {}
for _, row in ipairs(results) do
row['Has gem tags'] = m_util.string.split(row['Has gem tags'], '<MANY>')
for _, tag in ipairs(row['Has gem tags']) do
if tags[tag] == nil then
tags[tag] = {}
end
table.insert(tags[tag], row)
end
end
local tags_sorted = {}
for tag, _ in pairs(tags) do
table.insert(tags_sorted, tag)
end
table.sort(tags_sorted)
local tbl = mw.html.create('table')
tbl
:attr('class', 'wikitable sortable')
:tag('tr')
:tag('th')
:wikitext('Tag')
:done()
:tag('th')
:wikitext('Skills')
:done()
:done()
for _, tag in ipairs(tags_sorted) do
local rows = tags[tag]
local tr = tbl:tag('tr')
tr
:tag('td')
:wikitext(tag)
local td = tr:tag('td')
for i, row in ipairs(rows) do
td:wikitext(f_item_link{page=row[1], name=row['Has Name'], inventory_icon=row['Has inventory icon'], html=row['Has infobox HTML'] or ''})
if i < #rows then
td:wikitext('<br>')
end
end
end
return tostring(tbl)
end
-- ----------------------------------------------------------------------------
-- Misc. Item templates
-- ----------------------------------------------------------------------------
--
-- Template: Item acquisition
--
-- Used to duplicate the information from the infobox in a more readable manner on the page.
function p.item_acquisition (frame)
-- Get args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
local out = {}
local results
local query
-- -------------------------
-- Drop restrictions by area
-- -------------------------
results = m_util.cargo.query(
{'items'},
{'items.drop_areas_html'},
{
where=string.format('items._pageName="%s"', tpl_args.page),
-- Workaround: Fix cargo duplicates
groupBy='items._pageID',
}
)
if #results > 0 then
results = results[1]
if results['items.drop_areas_html'] then
local ul = mw.html.create('ul')
for _, area in ipairs(m_util.string.split(results['items.drop_areas_html'], ',%s*')) do
ul:tag('li')
:wikitext(area)
end
out[#out+1] = i18n.acquisition.area
out[#out+1]= '<br>'
out[#out+1] = tostring(ul)
end
end
-- ------------------------------------
-- Obtained via vendor recipes/upgrades
-- ------------------------------------
--
-- Query data
--
local sets = {}
results = m_util.cargo.query(
{'upgraded_from_sets'},
{
'upgraded_from_sets.set_id',
'upgraded_from_sets.text',
},
{
where=string.format('upgraded_from_sets._pageName="%s"', tpl_args.page),
-- Workaround: Fix cargo duplicates
groupBy='upgraded_from_sets._pageID,upgraded_from_sets.set_id',
}
)
for _, row in ipairs(results) do
row.groups = {}
sets[tonumber(row['upgraded_from_sets.set_id'])] = row
end
results = m_util.cargo.query(
{'upgraded_from_groups'},
{
'upgraded_from_groups.set_id',
'upgraded_from_groups.group_id',
'upgraded_from_groups.notes',
'upgraded_from_groups.amount',
'upgraded_from_groups.item_name',
'upgraded_from_groups.item_page',
},
{
where=string.format('upgraded_from_groups._pageName="%s"', tpl_args.page),
-- Workaround: Fix cargo duplicates
groupBy='upgraded_from_groups._pageID,upgraded_from_groups.set_id,upgraded_from_groups.group_id',
}
)
for _, row in ipairs(results) do
sets[tonumber(row['upgraded_from_groups.set_id'])].groups[tonumber(row['upgraded_from_groups.group_id'])] = row
end
--
-- Build output
--
if #sets > 0 then
local ul = mw.html.create('ul')
for _, set in ipairs(sets) do
local li = ul:tag('li')
if set['upgraded_from_sets.text'] then
li:wikitext(set['upgraded_from_sets.text'] .. '<br>')
end
local str = {}
for _, group in ipairs(set.groups) do
str[#str+1] = string.format('%sx [[%s|%s]]', group['upgraded_from_groups.amount'], group['upgraded_from_groups.item_page'], group['upgraded_from_groups.item_name'] or group['upgraded_from_groups.item_page'])
end
li:wikitext(table.concat(str, ', '))
end
out[#out+1] = i18n.acquisition.upgraded_from
out[#out+1]= '<br>'
out[#out+1] = tostring(ul)
end
out[#out+1] = tpl_args.acquisition_insert
-- -------------------------------------
-- Ingredient of vendor recipes/upgrades
-- -------------------------------------
--
-- Query
--
-- TODO: IL links
results = m_util.cargo.query(
{'upgraded_from_groups'},
{'upgraded_from_groups._pageName'},
{
where=string.format('upgraded_from_groups.item_page="%s"', tpl_args.page),
-- Only need each page name once
groupBy='upgraded_from_groups._pageName',
}
)
if #results > 0 then
local head = mw.html.create('h3')
head:wikitext(i18n.acquisition.ingredient_header)
out[#out+1] = tostring(head)
out[#out+1] = i18n.acquisition.ingredient
out[#out+1]= '<br>'
local ul = mw.html.create('ul')
for _, row in ipairs(results) do
ul:tag('li')
:wikitext(string.format('[[%s]]', row['upgraded_from_groups._pageName']))
end
out[#out+1] = tostring(ul)
end
out[#out+1] = tpl_args.ingredient_append
-- ------------------------------------
-- output
-- ------------------------------------
local head = mw.html.create('h2')
head:wikitext(i18n.acquisition.header .. '[[File:Questionmark.png|right|24px|link=Path_of_Exile_Wiki:How_to_edit_item_acquisition]]')
return tostring(head) .. table.concat(out)
end
--
-- Template:Item class
--
function p.item_class (frame)
-- Get args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
if not doInfoCard then
doInfoCard = require('Module:Infocard')._main
end
m_util.cast.factory.table('name', {key='full', tbl=m_game.constants.item.class})(tpl_args, frame)
if tpl_args.name_list ~= nil then
tpl_args.name_list = m_util.string.split(tpl_args.name_list, ',%s*')
else
tpl_args.name_list = {}
end
--
local ul = mw.html.create('ul')
for _, item in ipairs(tpl_args.name_list) do
ul
:tag('li')
:wikitext(item)
:done()
end
-- Output Infocard
local tplargs = {
['header'] = tpl_args.name,
['subheader'] = i18n.item_class_infobox.page .. i18n.item_class_infobox.info,
[1] = i18n.item_class_infobox.also_referred_to_as .. tostring(ul),
}
-- cats
local cats = {
'Item classes',
tpl_args.name,
}
-- Done
return doInfoCard(tplargs) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
end
-- ----------------------------------------------------------------------------
-- Debug stuff
-- ----------------------------------------------------------------------------
p.debug = {}
function p.debug._tbl_data(tbl)
keys = {}
for _, data in ipairs(core.result.generic_item) do
if type(data.arg) == 'string' then
keys[data.arg] = 1
elseif type(data.arg) == 'table' then
for _, arg in ipairs(data.arg) do
keys[arg] = 1
end
end
end
local out = {}
for key, _ in pairs(keys) do
out[#out+1] = string.format("['%s'] = '1'", key)
end
return table.concat(out, ', ')
end
function p.debug.generic_item_all()
return p.debug._tbl_data(core.result.generic_item)
end
function p.debug.skill_gem_all()
return p.debug._tbl_data(core.result.skill_gem_new)
end
return p