We are currently in the process of bringing this to 100% working functionality. Any help would be greatly appreciated. Find out more in the Community Portal.
Module:Item2
| This Lua module is used on a very large number of pages. To avoid large-scale disruption and unnecessary server load, any changes to this module should first be tested in its /sandbox or /testcases subpages. The tested changes can then be added to this page in one single edit. Please consider discussing any changes on the talk page before implementing them. |
| 30px | This module depends on the following other modules: |
The item module provides functionality for various item-related templates.
Overview
This module is responsible for creating item boxes, various item lists, item links and other item-related tasks. In the process a lot of the input data is verified and also added as semantic property to pages; as such, any templates deriving from this module should not be used on user pages other then for temporary testing purposes.
This template is also backed by an export script in PyPoE which can be used to export item data from the game files which then can be used on the wiki. Use the export when possible,
Item templates
Module:Item2
All templates defined in Module:Item2:
Module:Item table
All templates defined in Module:Item table:
- {{Item table}}
- {{Query base items}}
- {{Query unique items}}
- {{Item unique versions}}
- {{Area item drops}}
- {{Item table/skill gems}}
- {{Map item drops}}
- {{Prophecy description}}
- {{Simple item list}}
Module:Item link
All templates defined in Module:Item link:
- {{Item link}}
- {{Il}}
- {{Item icon link}}
Module:Item acquisition
- {{Item acquisition}}
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
-- SMW reworked item module
-- TODO:
-- included mods and base items should override the internal values and then set them as semantic properties so items can be queried for
-- > subobjects/property in display code
-- drop location (+drop difficulty) support
-- divinatation card support
-- talisman tier and category
-- currency: description, stack_size_currency_tab, cosmetic_type
-- potentially include quality bonus in calcuations
-- singular for weapon class in infoboxes
-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------
local xtable = require('Module:Table')
local util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local m_skill = require('Module:Skill')
local p = {}
local v = {}
local g_frame, g_args
local image_size = 39
-- ----------------------------------------------------------------------------
-- Other stuff
-- ----------------------------------------------------------------------------
local h = {}
function h.new_color(label, text)
if text == nil or text == '' then
return nil
end
return tostring(mw.html.create('span')
:attr('class', 'text-color -' .. label)
:wikitext(text))
end
function h.na_or_val(tr, value, func)
if value == nil then
tr:wikitext(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 = {
property = ' range minimum',
var = '_range_minimum',
},
max = {
property = ' range maximum',
var = '_range_maximum',
},
avg = {
property = ' range average',
var = '_range_average',
},
}
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
h.tbl = {}
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.range(args)
-- args: table
-- property
-- no_base: no base value given
-- options: table, see h.format_value
args.options = args.options or {}
return function (tr, value, data)
local value = {}
local chk_keys = {}
for key, range_data in pairs(h.range_map) do
value[key] = tonumber(data[string.format('?Has %s%s', args.property, range_data.property)])
chk_keys[#chk_keys+1] = key
end
if args.no_base == nil then
value.base = tonumber(data['?Has base ' .. args.property])
chk_keys[#chk_keys+1] = 'base'
end
if util.table.has_one_value(value, chk_keys, nil) then
tr:wikitext(util.html.td.na())
return
end
if args.no_base or value.avg == value.base then
args.options.color = 'value'
else
args.options.color = 'mod'
end
tr
:tag('td')
:attr('data-sort-value', value.avg)
:wikitext(h.format_value(value, args.options))
:done()
end
end
function h.format_value(value, options)
-- value: table
-- min:
-- max:
-- options: table
-- fmt: formatter to use for the value instead of valfmt
-- before: add this string before the coloured string with colour gray
-- after: add this string after the coloured string with colour gray
-- func: Function to adjust the value with before output
-- color: colour code for h.new_color, overrides mod colour
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
if options.func ~= nil then
value.min = options.func(value.min)
value.max = options.func(value.max)
end
if options.fmt == nil then
options.fmt = '%s'
elseif type(options.fmt) == 'function' then
options.fmt = options.fmt()
end
if value.min == value.max then
value.out = string.format(options.fmt, value.min)
else
value.out = string.format(string.format("(%s to %s)", options.fmt, options.fmt), value.min, value.max)
end
value.out = h.new_color(value.color, value.out)
local before = ''
if type(options.before) == 'string' then
before = h.new_color('default', options.before)
elseif type(options.before) == 'function' then
before = h.new_color('default', options.before())
end
local after = ''
if type(options.after) == 'string' then
after = h.new_color('default', options.after)
elseif type(options.after) == 'function' then
after = h.new_color('default', options.after())
end
return before .. value.out .. after
end
-- ----------------------------------------------------------------------------
-- core
-- ----------------------------------------------------------------------------
local core = {}
function core.validate_mod(args)
local value = g_args[args.key .. args.i]
local out = {
result=nil,
type=args.key,
is_text=false,
}
if value ~= nil then
--semantic search
local query = {
string.format('[[Is mod::%s]]', value),
'?#=Page',
'?Is mod#',
'?Has mod group#',
'?Has mod type#',
'?Has stat text#',
'?Has level requirement#',
}
out.result = util.smw.query(query, g_frame)
if out.result == nil or #out.result == 0 then
error(string.format('Mod "%s" found', value))
end
out.result = out.result[1]
query = {
string.format('[[-Has subobject::%s]] [[Is stat number::+]]', out.result[1]),
'?Is stat number#',
'?Has stat id#',
'?Has minimum stat value#',
'?Has maximum stat value#',
}
out.result.stats = util.smw.query(query, g_frame)
table.insert(g_args._properties['Has mod ids'], value)
--table.insert(g_args._subobjects, {
-- ['Is mod number'] = args.i,
-- ['Has mod id'] = g_args[value],
--})
-- process and cache stats
local id, value
for _, stat in ipairs(out.result.stats) do
id = stat['Has stat id']
value = {
min = tonumber(stat['Has minimum stat value']),
max = tonumber(stat['Has maximum stat value']),
}
value.avg = (value.min+value.max)/2
if g_args._stats[id] == nil then
value.references = {out.result['Is Mod']}
g_args._stats[id] = value
else
table.insert(g_args._stats[id].references, out.result['Is Mod'])
g_args._stats[id].min = g_args._stats[id].min + value.min
g_args._stats[id].max = g_args._stats[id].max + value.max
g_args._stats[id].avg = g_args._stats[id].avg + value.avg
end
end
-- update item level requirement
local keys = {'required_level_final'}
-- only update base item requirement if this is an implicit
if args.key == 'implicit' then
keys[#keys+1] = 'required_level'
end
for _, key in ipairs(keys) do
local req = math.floor(tonumber(out.result['Has level requirement']) * 0.8)
if req > g_args[key] then
g_args[key] = req
end
end
else
value = g_args[args.key .. args.i .. '_text']
if value ~= nil then
--table.insert(g_args._subobjects, {
-- ['Is mod number'] = args.i,
-- ['Has mod text'] = value,
--})
out.is_text = true
out.result = value
end
end
if out.result ~= nil then
table.insert(g_args._mods, out)
return true
else
return false
end
end
function core.process_arguments(args)
for _, k in ipairs(args.array) do
table.insert(g_args._total_args, k)
local data = core.map[k]
if data.func ~= nil then
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
end
end
function core.process_mod_stats(args)
lines = {}
for _, modinfo in ipairs(g_args._mods) do
if modinfo.type == args.type then
if modinfo.is_text then
table.insert(lines, modinfo.result)
else
table.insert(lines, modinfo.result['Has stat text'])
end
end
end
if #lines == 0 then
return
else
return table.concat(lines, '<br>')
end
end
function core.err(args)
local err = mw.html.create('div')
err
:attr('style', 'font-color: red; font-weight: bold;')
:wikitext(args.msg or ('Argument ' .. args.key .. ' to item template is invalid. Please check the documention for acceptable values.'))
:done()
return tostring(err)
end
--
-- function factory
--
core.factory = {}
function core.factory.array_table_cast(k, args)
-- Arguments:
--
-- tbl
-- errmsg
return function ()
local elements
if g_args[k] ~= nil then
elements = util.string.split(g_args[k], ', ')
for _, element in ipairs(elements) do
local r = util.table.find_in_nested_array{value=element, tbl=args.tbl, key='full'}
if r == nil then
error(core.err{msg=string.format(args.errmsg, element)})
end
end
g_args[k] = xtable:new(elements)
end
end
end
function core.factory.table_cast(k, args)
return function ()
args.value = g_args[k]
g_args[k] = util.table.find_in_nested_array(args)
if g_args[k] == nil then
error(core.err{key=k})
end
end
end
function core.factory.number_cast(k, default)
return function ()
g_args[k] = tonumber(g_args[k]) or default
end
end
function core.factory.boolean_cast(k, default)
return function()
if g_args[k] == nil then
g_args[k] = default
else
g_args[k] = util.cast.boolean(g_args[k])
end
end
end
function core.factory.percentage(k)
return function ()
local v = tonumber(g_args[k])
if v == nil then
return core.err{key=k}
end
if v < 0 or v > 100 then
return core.err{msg=k .. ' must be in range 0-100.'}
end
g_args[k] = v
end
end
function core.factory.display_value(args)
-- TODO: sane defaults for the prepend string "before" & after arguments
--
-- args:
-- args<Array>: Array of arguments to use for displaying
-- values<Array>: Array of base values to use for displaying; useful if no args are present
-- options<Array>:
-- fmt: formatter to use for the value instead of valfmt
-- allow_zero: allow zero values
-- before: add this string before the coloured string with colour gray
-- after: add this string after the coloured string with colour gray
-- func: Function to adjust the value with before output
-- hide_default: hide the value if this is set;
-- color: colour code for h.new_color, overrides mod colour
for k, default in pairs({stats = {}, options = {}}) do
if args[k] == nil then
args[k] = default
end
end
local size
if args.keys ~= nil then
size = #args.keys
elseif args.values ~= nil then
size = #args.values
end
if size then
for i=1, size do
if args.options[i] == nil then
args.options[i] = {}
end
end
else
error('invalid data')
end
return function ()
local base_values = {}
local temp_values = {}
if args.type == 'gem' then
if not core.class_groups.gems.keys[g_args.class] then
return
end
for i, k in ipairs(args.keys) do
local value = g_args['static_' .. k]
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=g_args[string.format('level1_%s', k)],
max=g_args[string.format('level%s_%s', g_args.max_level, k)],
}
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
else
for i, k in ipairs(args.keys) do
base_values[i] = g_args[k]
local value = {}
if g_args[k .. '_range_minimum'] ~= nil then
value.min = g_args[k .. '_range_minimum']
value.max = g_args[k .. '_range_maximum']
elseif g_args[k] ~= nil then
value.min = g_args[k]
value.max = g_args[k]
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 v = data.value
if opt.hide_default ~= nil and opt.hide_default == v.min and opt.hide_default == v.max then
else
table.insert(final_values, data)
end
end
-- all zeros = dont display and return early
if #final_values == 0 then
return nil
end
local out = {}
if args.before then
out[#out+1] = h.new_color('default', args.before)
end
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(value, options)
end
if args.after then
out[#out+1] = h.new_color('default', args.after)
end
return table.concat(out, '')
end
end
function core.factory.display_value_only(key)
return function()
return g_args[key]
end
end
function core.factory.descriptor_value(args)
-- Arguments:
-- key
-- tbl
args = args or {}
return function (value)
args.tbl = args.tbl or g_args
if args.tbl[args.key] then
value = util.html.abbr(value, args.tbl[args.key])
end
return value
end
end
--
-- argument mapping
--
core.map = {
-- special params
html = {
property = 'Has infobox HTML',
func = nil,
},
implicit_stat_text = {
property = 'Has implicit stat text',
func = function ()
g_args.implicit_stat_text = core.process_mod_stats{type='implicit'}
end,
},
explicit_stat_text = {
property = 'Has explicit stat text',
func = function ()
g_args.explicit_stat_text = core.process_mod_stats{type='explicit'}
if g_args.is_talisman then
g_args.explicit_stat_text = (g_args.explicit_stat_text or '') .. h.new_color('corrupted', 'Corrupted')
end
end,
},
stat_text = {
property = 'Has stat text',
func = function()
local text = (g_args.implicit_stat_text or '') .. (g_args.explicit_stat_text or '')
if string.len(text) > 0 then
g_args.stat_text = text
end
end,
},
-- generic
class = {
property = 'Has item class',
func = core.factory.table_cast('class', {key='full', tbl=m_game.constants.item.class}),
},
rarity = {
property = 'Has rarity',
func = core.factory.table_cast('rarity', {key={'full', 'long_lower'}, tbl=m_game.constants.item.rarity, rtrkey='full'}),
},
name = {
property = 'Has name',
func = nil,
},
size_x = {
property = 'Has inventory width',
func = core.factory.number_cast('size_x'),
},
size_y = {
property = 'Has inventory height',
func = core.factory.number_cast('size_y'),
},
drop_enabled = {
property = 'Is drop enabled',
func = core.factory.boolean_cast('drop_enabled', true),
},
drop_level = {
property = 'Has drop level',
func = core.factory.number_cast('drop_level'),
},
required_level = {
property = 'Has base level requirement',
func = core.factory.number_cast('required_level', 1),
},
required_level_final = {
property = 'Has level requirement',
func = function ()
g_args.required_level_final = g_args.required_level
end,
},
required_dexterity = {
property = 'Has base dexterity requirement',
func = core.factory.number_cast('required_dexterity', 0),
},
required_strength = {
property = 'Has base strength requirement',
func = core.factory.number_cast('required_strength', 0),
},
required_intelligence = {
property = 'Has base intelligence requirement',
func = core.factory.number_cast('required_intelligence', 0),
},
inventory_icon = {
property = 'Has inventory icon',
func = function ()
g_args.inventory_icon_id = g_args.inventory_icon or g_args.name
g_args.inventory_icon = string.format('File:%s inventory icon.png', g_args.inventory_icon_id)
end,
},
-- note: this must be called after inventory item to work correctly as it depends on g_args.inventory_icon_id being set
alternate_art_inventory_icons = {
property = 'Has alternate inventory icons',
func = function ()
local icons = {}
if g_args.alternate_art_inventory_icons ~= nil then
local names = util.string.split(g_args.alternate_art_inventory_icons, ', ')
for _, name in ipairs(names) do
icons[#icons+1] = string.format('File:%s %s inventory icon.png', g_args.inventory_icon_id, name)
end
end
g_args.alternate_art_inventory_icons = icons
end,
},
help_text = {
property = 'Has help text',
func = nil,
},
flavour_text = {
property = 'Has flavour text',
func = nil,
},
tags = {
property = 'Has tags',
func = core.factory.array_table_cast('tags', {
tbl = m_game.constants.tags,
errmsg = '%s is not a valid tag',
}),
},
metadata_id = {
property = 'Has metadata id',
func = nil,
},
-- For rarity != normal, rarity already verified
base_item = {
property = 'Has base item',
func = function ()
if g_args['rarity'] ~= 'Normal' then
local query = {
string.format('[[Has name::%s]]', g_args['base_item']),
string.format('[[Has item class::%s]]', g_args['class']),
'[[Has rarity::normal]]',
}
for _, k in ipairs(g_args['total_args']) do
query[#query+1] = string.format('?%s#', core.map[k].property)
end
local result = util.smw.query(query, g_frame)
if #result > 1 then
error(core.err{msg='More then one result found for the specified base item.'})
-- TODO be more explicit in the error?
end
g_args['base_item'] = result[1]
-- TODO Semantic search - verify ?
elseif g_args['base_item'] ~= nil then
error(core.err{msg='Base item requries item with rarity above normal.'})
end
end,
},
--
-- specific section
--
-- amulets
is_talisman = {
property = 'Is talisman',
func = core.factory.boolean_cast('is_talisman')
},
-- flasks
charges_max = {
property = 'Has base maximum flask charges',
func = core.factory.number_cast('charges_max'),
},
charges_per_use = {
property = 'Has base flask charges per use',
func = core.factory.number_cast('changes_per_use'),
},
flask_mana = {
property = 'Has base flask mana recovery',
func = core.factory.number_cast('flask_mana'),
},
flask_life = {
property = 'Has base flask life recovery',
func = core.factory.number_cast('flask_life'),
},
flask_duration = {
property = 'Has base flask duration',
func = core.factory.number_cast('flask_duration'),
},
-- weapons & active skills
critical_strike_chance = {
property = 'Has base critical strike chance',
func = core.factory.number_cast('critical_strike_chance'),
},
-- weapons
attack_speed = {
property = 'Has base attack speed',
func = core.factory.number_cast('attack_speed'),
},
damage_min = {
property = 'Has base minimum physical damage',
func = core.factory.number_cast('damage_min'),
},
damage_max = {
property = 'Has base maximum physical damage',
func = core.factory.number_cast('damage_max'),
},
range = {
property = 'Has base weapon range',
func = core.factory.number_cast('range'),
},
-- armor-type stuff
armour = {
property = 'Has base armour',
func = core.factory.number_cast('armour', 0),
},
energy_shield = {
property = 'Has base energy shield',
func = core.factory.number_cast('energy_shield', 0),
},
evasion = {
property = 'Has base evasion',
func = core.factory.number_cast('evasion', 0),
},
-- shields
block = {
property = 'Has base block',
func = core.factory.number_cast('block'),
},
-- skill gem stuff
gem_description = {
property = 'Has description',
func = nil,
},
dexterity_percent = {
property = 'Has dexterity percentage',
func = core.factory.percentage('dexterity_percent'),
},
strength_percent = {
property = 'Has strength percentage',
func = core.factory.percentage('strength_percent'),
},
intelligence_percent = {
property = 'Has intelligence percentage',
func = core.factory.percentage('intelligence_percent'),
},
primary_attribute = {
property = 'Has primary attribute',
func = function()
for _, attr in ipairs(m_game.constants.attributes) do
local val = g_args[attr.long_lower .. '_percent']
if val and val >= 60 then
g_args['primary_attribute'] = attr.long_upper
return
end
end
g_args['primary_attribute'] = 'None'
end,
},
gem_tags = {
property = 'Has gem tags',
func = core.factory.array_table_cast('gem_tags', {
tbl = m_game.constants.item.gem_tags,
errmsg = '%s is not a valid tag',
}),
},
experience = {
property = 'Has maximum experience',
func = core.factory.percentage('experience'),
},
skill_screenshot = {
property = 'Has skill screenshot',
func = function ()
g_args.skill_screenshot = string.format('File:%s skill screenshot.jpg', g_args.name)
end,
},
-- Active gems only
gem_icon = {
property = 'Has skill gem icon',
func = function ()
-- TODO readd support if needed.
g_args.gem_icon = string.format('File:%s skill icon.png', g_args.name)
end,
},
-- Support gems only
support_gem_letter_html = {
property = 'Has support gem letter HTML',
func = function ()
if g_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 g_args[k] and g_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(g_args.support_gem_letter)
:done()
g_args.support_gem_letter_html = tostring(container)
end
end,
},
-- Maps
map_tier = {
property = 'Is map tier',
func = core.factory.number_cast('tier'),
},
map_area = {
property = 'Has map area id',
func = nil, -- TODO: Validate against a query?
},
map_guild_letter = {
property = 'Has map guild letter',
func = nil,
},
unique_map_area = {
property = 'Has unique map area id',
func = nil, -- TODO: Validate against a query?
},
unique_map_guild_letter = {
property = 'Has unqiue map guild letter',
func = nil,
},
-- Currency-like items
stack_size = {
property = 'Has stack size',
func = core.factory.number_cast('stack_size'),
},
stack_size_currency_tab = {
property = 'Has currency tab stack size',
func = core.factory.number_cast('stack_size_currency_tab'),
},
description = {
property = 'Has description',
func = nil,
},
cosmetic_type = {
property = 'Has cosmetic type',
func = nil,
},
-- hideout doodads (HideoutDoodads.dat)
is_master_doodad = {
property = 'Is master doodad',
func = core.factory.boolean_cast('is_master_doodad'),
},
master = {
property = 'Is sold by master',
-- todo validate against list of master names
func = core.factory.table_cast('master', {key='full', tbl=m_game.constants.masters}),
},
master_level_requirement = {
property = 'Has master level requirement',
func = core.factory.number_cast('master_level_requirement'),
},
master_favour_cost = {
property = 'Has master favour cost',
func = core.factory.number_cast('master_favour_cost'),
},
variation_count = {
property = 'Has number of variations',
func = core.factory.number_cast('variation_count'),
},
--
-- derived stats
--
name_list = {
property = 'Has names',
func = function ()
if g_args.name_list ~= nil then
g_args.name_list = util.string.split(g_args.name_list, ', ')
g_args.name_list[#g_args.name_list+1] = g_args.name
else
g_args.name_list = {g_args.name}
end
end,
},
name_list_lower = {
property = 'Has lowercase names',
func = function()
g_args.name_list_lower = {}
for index, value in ipairs(g_args.name_list) do
g_args.name_list_lower[index] = string.lower(value)
end
end
},
gem_tags_difference = {
property = 'Has gem tags difference',
func = function()
if g_args.gem_tags ~= nil then
local gtags = {}
-- copy tags
for _, data in ipairs(m_game.constants.item.gem_tags) do
if data.full and data.full ~= '' then
gtags[data.full] = true
end
end
-- delete existing tags
for _, tag in ipairs(g_args.gem_tags) do
gtags[tag] = nil
end
-- add them as ordered list and not as hash table so it is consistent with the other gem tag list
g_args.gem_tags_difference = {}
for key, value in pairs(gtags) do
g_args.gem_tags_difference[#g_args.gem_tags_difference+1] = key
end
end
end
},
}
core.stat_map = {
damage_min = {
property = 'Has minimum physical damage',
stats_add = {
'local_minimum_added_physical_damage',
},
stats_increased = {
'local_physical_damage_+%',
},
},
damage_max = {
property = 'Has maximum physical damage',
stats_add = {
'local_minimum_added_physical_damage',
},
stats_increased = {
'local_physical_damage_+%',
},
},
fire_damage_min = {
default = 0,
property = 'Has minimum fire damage',
stats_add = {
'local_minimum_added_fire_damage',
},
},
fire_damage_max = {
default = 0,
property = 'Has maximum fire damage',
stats_add = {
'local_maximum_added_fire_damage',
},
},
cold_damage_min = {
default = 0,
property = 'Has minimum cold damage',
stats_add = {
'local_minimum_added_cold_damage',
},
},
cold_damage_max = {
default = 0,
property = 'Has maximum cold damage',
stats_add = {
'local_maximum_added_cold_damage',
},
},
lightning_damage_min = {
default = 0,
property = 'Has minimum lightning damage',
stats_add = {
'local_minimum_added_lightning_damage',
},
},
lightning_damage_max = {
default = 0,
property = 'Has maximum lightning damage',
stats_add = {
'local_maximum_added_lightning_damage',
},
},
chaos_damage_min = {
default = 0,
property = 'Has minimum chaos damage',
stats_add = {
'local_minimum_added_chaos_damage',
},
},
chaos_damage_max = {
default = 0,
property = 'Has maximum chaos damage',
stats_add = {
'local_maximum_added_chaos_damage',
},
},
critical_strike_chance = {
property = 'Has critical strike chance',
stats_add = {
'local_critical_strike_chance',
},
stats_increased = {
'local_critical_strike_chance_+%',
},
},
attack_speed = {
property = 'Has attack speed',
stats_increased = {
'local_attack_speed_+%',
},
},
map_item_quantity = {
property = 'Has map item quantity',
stats_add = {
'map_item_drop_quantity_+%',
},
},
map_item_rarity = {
property = 'Has map item rarity',
stats_add = {
'map_item_drop_rarity_+%',
},
},
map_pack_size = {
property = 'Has map pack size',
stats_add = {
'map_pack_size_+%',
},
},
flask_life = {
property = 'Has flask mana recovery',
stats_add = {
'local_flask_life_to_recover',
},
stats_increased = {
'local_flask_life_to_recover_+%',
},
},
flask_mana = {
property = 'Has flask mana recovery',
stats_add = {
'local_flask_mana_to_recover',
},
stats_increased = {
'local_flask_mana_to_recover_+%',
},
},
flask_duration = {
property = 'Has flask duration',
stats_increased = {
'local_flask_recovery_speed_+%',
},
},
charges_per_use = {
property = 'Has flask charges per use',
stats_increased = {
'local_charges_used_+%',
},
},
charges_cap = {
property = 'Has flask maximum charges',
stats_add = {
'local_extra_max_charges',
},
stats_increased = {
'local_max_charges_+%',
'local_charges_added_+%',
},
},
block = {
property = 'Has block',
stats_add = {
'local_additional_block_chance_%',
},
},
armour = {
property = 'Has 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_+%',
}
},
evasion = {
property = 'Has 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_+%',
},
},
energy_shield = {
property = 'Has 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_+%',
},
},
}
core.dps_map = {
{
name = 'physical_dps',
property = 'physical damage per second',
damage_args = {'damage', },
label = util.html.abbr('pDPS', 'physical damage per second'),
},
{
name = 'fire_dps',
property = 'fire damage per second',
damage_args = {'fire_damage'},
label = util.html.abbr('Fire DPS', 'fire damage per second'),
},
{
name = 'cold_dps',
property = 'cold damage per second',
damage_args = {'cold_damage'},
label = util.html.abbr('Cold DPS', 'cold damage per second'),
},
{
name = 'lightning_dps',
property = 'lightning damage per second',
damage_args = {'lightning_damage'},
label = util.html.abbr('Light. DPS', 'lightning damage per second'),
},
{
name = 'chaos_dps',
property = 'chaos damage per second',
damage_args = {'chaos_damage'},
label = util.html.abbr('Chaos DPS', 'chaos damage per second'),
},
{
name = 'elemental_dps',
property = 'elemental damage per second',
damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'},
label = util.html.abbr('eDPS', 'elemental damage (i.e. fire/cold/lightning) per second'),
},
{
name = 'poison_dps',
property = 'poison damage per second',
damage_args = {'damage', 'chaos_damage'},
label = util.html.abbr('Poison DPS', 'poison damage (i.e. physcial/chaos) per second'),
},
{
name = 'dps',
property = 'damage per second',
damage_args = {'damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'},
label = util.html.abbr('DPS', 'total damage (i.e. physcial/fire/cold/lightning/chaos) per second'),
},
}
-- base item is default, but will be validated later
-- Notes:
-- inventory_icon must always be before alternate_art_inventory_icons
core.default_args = {'class', 'rarity', 'name', 'name_list', 'name_list_lower', 'size_x', 'size_y', 'drop_enabled', 'drop_level', 'required_level', 'required_level_final', 'inventory_icon', 'alternate_art_inventory_icons', 'flavour_text', 'help_text', 'tags', 'metadata_id'}
core.late_args = {'implicit_stat_text', 'explicit_stat_text', 'stat_text'}
core.class_groups = {
flasks = {
keys = {['Life Flasks'] = true, ['Mana Flasks'] = true, ['Hybrid Flasks'] = true, ['Utility Flasks'] = true, ['Critical Utility Flasks'] = true},
args = {'flask_duration', 'charges_max', 'charges_per_use'},
},
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 = {'required_dexterity', 'required_intelligence', 'required_strength', 'critical_strike_chance', 'attack_speed', 'damage_min', 'damage_max', 'range'},
},
gems = {
keys = {['Active Skill Gems'] = true, ['Support Skill Gems'] = true},
args = {'dexterity_percent', 'strength_percent', 'intelligence_percent', 'primary_attribute', 'gem_tags', 'gem_tags_difference'},
},
armor = {
keys = {['Gloves'] = true, ['Boots'] = true, ['Body Armours'] = true, ['Helmets'] = true, ['Shields'] = true},
args = {'required_dexterity', 'required_intelligence', 'required_strength', 'armour', 'energy_shield', 'evasion'},
},
stackable = {
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'},
},
['Life Flasks'] = {
args = {'flask_life'},
},
['Mana Flasks'] = {
args = {'flask_mana'},
},
['Hybrid Flasks'] = {
args = {'flask_life', 'flask_mana'},
},
['Active Skill Gems'] = {
args = {'skill_screenshot', 'gem_icon'},
defaults = {
help_text = 'Place into an item socket of the right colour to gain this skill. Right click to remove from a socket.',
size_x = 1,
size_y = 1,
},
},
['Support Skill Gems'] = {
args = {'support_gem_letter_html'},
defaults = {
help_text = '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.',
size_x = 1,
size_y = 1,
},
},
['Shields'] = {
args = {'block'},
},
['Maps'] = {
args = {'map_tier', 'map_area', 'map_guild_letter', 'unique_map_area', 'unique_map_guild_letter'},
},
['Hideout Doodads'] = {
args = {'is_master_doodad', 'master', 'master_level_requirement', 'master_favour_cost', 'variation_count'},
defaults = {
help_text = 'Right click on this item then left click on a location on the ground to create the object.',
},
},
['Jewel'] = {
defaults = {
help_text = 'Place into an allocated Jewel Socket on the Passive Skill Tree. Right click to remove from the Socket.',
},
},
}
-- add defaults from class specifics and class groups
core.item_classes = {}
for _, data in ipairs(m_game.constants.item.class) do
core.item_classes[data['full']] = {
args = xtable:new(),
defaults = {},
}
end
for _, row in pairs(core.class_groups) do
for k, _ in pairs(row.keys) do
core.item_classes[k].args:insertT(row.args)
end
end
for k, row in pairs(core.class_specifics) do
if row.args ~= nil then
core.item_classes[k].args:insertT(row.args)
end
if row.defaults ~= nil then
for key, value in pairs(row.defaults) do
core.item_classes[k].defaults[key] = value
end
end
end
-- GroupTable -> RowTable -> formatter function
--
--
core.display_groups = {
-- Tags, stats, level, etc
{
{
args = function ()
if g_args.class == nil then
return false
end
return core.class_groups.weapons.keys[g_args.class] ~= nil
end,
func = core.factory.display_value{
keys={'class'},
options = {
[1] = {
color = 'default',
},
},
},
},
{
args = {'gem_tags'},
func = function ()
local out = {}
for i, tag in ipairs(g_args.gem_tags) do
out[#out+1] = string.format('[[:Category:%s (gem tag)|%s]]', tag, tag)
end
return table.concat(out, ', ')
end,
},
{
args = {'support_gem_letter_html'},
func = core.factory.display_value{
keys={'support_gem_letter_html'},
options = {
[1] = {
before = 'Icon: ',
},
},
},
},
{
args = {'radius'},
func = core.factory.display_value{
keys={'radius', 'radius_secondary', 'radius_tertiary'},
options = {
[1] = {
before = 'Radius: ',
func = core.factory.descriptor_value{key='radius_description'},
},
[2] = {
before = ' / ',
func = core.factory.descriptor_value{key='radius_secondary_description'},
},
[3] = {
before = ' / ',
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{
keys={'mana_cost'},
type='gem',
options = {
[1] = {
hide_default = 100,
fmt = function ()
if g_args.has_percentage_mana_cost then
return '%i%%'
else
return '%i'
end
end,
before = function ()
if g_args.has_reservation_mana_cost then
return 'Mana Reserved: '
else
return 'Mana Cost: '
end
end,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
keys={'mana_multiplier'},
type='gem',
options = {
[1] = {
hide_default = 100,
fmt = '%i%%',
before = 'Mana Multiplier: ',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
keys={'vaal_souls_requirement', 'vaal_souls_requirement', 'vaal_souls_requirement'},
type='gem',
options = {
[1] = {
hide_default = 0,
fmt = '%i (N) / ',
before = 'Souls per use: ',
},
[2] = {
hide_default = 0,
fmt = '%i (C) / ',
func = function (value)
return value*1.5
end,
},
[3] = {
hide_default = 0,
fmt = '%i (M)',
func = function (value)
return value*2
end,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
keys={'vaal_stored_uses'},
type='gem',
options = {
[1] = {
hide_default = 0,
fmt = '%i',
before = 'Can store ',
after = ' use(s)',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
keys={'stored_uses'},
type='gem',
options = {
[1] = {
hide_default = 0,
fmt = '%i',
before = 'Can store ',
after = ' use(s)',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
keys={'cooldown'},
type='gem',
options = {
[1] = {
hide_default = 0,
fmt = '%.2f sec',
before = 'Cooldown Time: ',
},
},
},
},
{
args = {'cast_time'},
func = core.factory.display_value{
keys={'cast_time'},
options = {
[1] = {
hide_default = 0,
fmt = '%.2f sec',
before = 'Cast Time: ',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
keys={'critical_strike_chance'},
type='gem',
options = {
[1] = {
hide_default = 0,
fmt = '%.2f%%',
before = 'Critical Strike Chance: ',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
keys={'damage_effectiveness'},
options = {
[1] = {
hide_default = 100,
fmt = '%i%%',
before = 'Damage Effectiveness: ',
},
},
},
},
{
args = {'projectile_speed'},
func = core.factory.display_value{
keys={'projectile_speed'},
options = {
[1] = {
before = 'Projectile Speed: ',
},
},
},
},
-- Weapon only
{
args = {'damage_min', 'damage_max'},
func = core.factory.display_value{
keys={'damage_min', 'damage_max'},
options = {
[1] = {
fmt = '%i',
after = '–',
before = 'Physical Damage: '
},
[2] = {
fmt = '%i',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
before='Elemental Damage: ',
keys = {'fire_damage_min', 'fire_damage_max', 'cold_damage_min', 'cold_damage_max', 'lightning_damage_min', 'lightning_damage_max'},
options = {
[1] = {
hide_default = 0,
fmt = '%i',
after = h.new_color('fire', '–'),
color = 'fire',
},
[2] = {
hide_default = 0,
fmt = '%i',
after = ' ',
color = 'fire',
},
[3] = {
hide_default = 0,
fmt = '%i',
after = h.new_color('cold', '–'),
color = 'cold',
},
[4] = {
hide_default = 0,
fmt = '%i',
after = ' ',
color = 'cold',
},
[5] = {
hide_default = 0,
fmt = '%i',
after = h.new_color('lightning', '–'),
color = 'lightning',
},
[6] = {
hide_default = 0,
fmt = '%i',
color = 'lightning',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
before='Chaos Damage: ',
keys = {'chaos_damage_min', 'chaos_damage_max'},
options = {
[1] = {
hide_default = 0,
fmt = '%i',
after = h.new_color('chaos', '–'),
color = 'chaos',
},
[2] = {
hide_default = 0,
fmt='%i',
color = 'chaos',
},
},
},
},
{
args = {'critical_strike_chance'},
func = core.factory.display_value{
keys={'critical_strike_chance'},
options = {
[1] = {
fmt = '%.2f%%',
before = 'Critical Strike Chance: ',
},
},
},
},
{
args = {'attack_speed'},
func = core.factory.display_value{
keys={'attack_speed'},
options = {
[1] = {
fmt = '%.2f',
before = 'Attacks per Second: ',
},
},
},
},
-- Map only
{
args = {'map_level'},
func = core.factory.display_value{
keys={'map_level'},
options = {
[1] = {
--property = 'Has map level',
fmt = '%i',
before = 'Map Level: ',
},
},
},
},
{
args = {'map_item_quantity'},
func = core.factory.display_value{
keys={'map_item_quantity'},
options = {
[1] = {
fmt = '%i',
before = 'Item Quantity: ',
},
},
},
},
{
args = {'map_item_rarity'},
func = core.factory.display_value{
keys={'map_item_rarity'},
options = {
[1] = {
fmt = '%i',
before = 'Item Rarity: ',
},
},
},
},
{
args = {'map_pack_size'},
func = core.factory.display_value{
keys={'map_pack_size'},
options = {
[1] = {
fmt = '%i',
before = 'Pack Size: ',
},
},
},
},
-- Jewel Only
{
args = {'limit'},
func = core.factory.display_value{
keys={'limit'},
options = {
[1] = {
fmt = '%i',
before = 'Limit: ',
},
},
},
},
{
args = {'jewel_radius'},
func = core.factory.display_value{
keys={'jewel_radius'},
options = {
[1] = {
fmt = '%i',
before = 'Radius: ',
},
},
},
},
-- Flask only
{
args = {'flask_mana', 'flask_duration'},
--func = core.factory.display_flask('flask_mana'),
func = core.factory.display_value{
keys = {'flask_mana', 'flask_duration'},
options = {
[1] = {
fmt = '%i Mana',
before = 'Recovers',
},
[2] = {
fmt = '%.2f',
before = ' over ',
after = ' seconds',
},
}
},
},
{
args = {'flask_life', 'flask_duration'},
func = core.factory.display_value{
keys = {'flask_life', 'flask_duration'},
options = {
[1] = {
fmt = '%i Life',
before = 'Recovers',
},
[2] = {
fmt = '%.2f',
before = ' over ',
after = ' seconds',
},
}
},
},
{
-- don't display for mana/life flasks
args = function ()
for _, k in ipairs({'flask_life', 'flask_mana'}) do
if g_args[k] ~= nil then
return false
end
end
return g_args['flask_duration'] ~= nil
end,
func = core.factory.display_value{
keys = {'flask_duration'},
options = {
[1] = {
before = 'Lasts ',
after = ' Seconds',
fmt = '%.2f',
},
},
},
},
{
args = {'charges_per_use', 'charges_cap'},
func = core.factory.display_value{
keys = {'charges_per_use', 'charges_cap'},
options = {
[1] = {
before = 'Consumes ',
fmt = '%i',
},
[2] = {
before = ' of ',
after = ' Charges on use',
fmt = '%i',
},
},
},
},
-- armor
{
args = {'block'},
func = core.factory.display_value{
keys={'block'},
options = {
[1] = {
before = 'Block: ',
fmt = '%i%%',
hide_default = 0,
},
},
},
},
{
args = {'armour'},
func = core.factory.display_value{
keys={'armour'},
options = {
[1] = {
before = 'Armour: ',
fmt = '%i',
hide_default = 0,
},
},
},
},
{
args = {'evasion'},
func = core.factory.display_value{
keys={'evasion'},
options = {
[1] = {
before = 'Evasion: ',
fmt = '%i',
hide_default = 0,
},
},
},
},
{
args = {'energy_shield'},
func = core.factory.display_value{
keys={'energy_shield'},
options = {
[1] = {
before = 'Energy Shield: ',
fmt = '%i',
hide_default = 0,
},
},
},
},
-- Misc
{
args = {'stack_size'},
func = core.factory.display_value{
keys={'stack_size'},
options = {
[1] = {
--property = 'Has stack size',
fmt = '%i',
before = 'Stack Size: ',
},
},
},
},
},
-- Requirements
{
{
args = {'master', 'master_level_requirement'},
func = function()
-- masters have been validated before
local data
for i, rowdata in ipairs(m_game.constants.masters) do
if g_args.master == rowdata.full then
data = rowdata
break
end
end
return h.new_color('default', 'Requires ') .. string.format('[[%s|%s %s]]', data.full, data.short_upper, g_args.master_level_requirement)
end
},
{
args = function ()
if g_args.drop_enabled == true then
return false
end
return true
end,
func = function()
local span = mw.html.create('span')
span
:attr('class', 'infobox-disabled-drop')
:wikitext('DROP DISABLED')
:done()
return tostring(span)
end,
},
-- Instead of item level, show drop level if any
{
args = function ()
if g_args.drop_enabled == false then
return false
end
return g_args.drop_level ~= nil
end,
func = core.factory.display_value{
keys={'drop_level'},
options = {
[1] = {
fmt = '%i',
before = 'Drop Level: ',
},
},
},
},
{
args = nil,
func = function ()
local requirements = {}
local attr_label
local use_short_label
if g_args.required_level_final and g_args.required_level_final > 1 then
table.insert( requirements, 'Level ' .. tostring( h.new_color('value', g_args.required_level_final) ) )
end
for _, attr in ipairs(m_game.constants.attributes) do
local val = g_args['required_' .. attr['long_lower']]
if val and val > 0 then
use_short_label = false or g_args.required_level_final
for _, attr2 in ipairs(m_game.constants.attributes) do
if attr ~= attr2 then
use_short_label = use_short_label or g_args[attr2['long_lower']]
end
end
if use_short_label then
attr_label = attr['short_upper']
else
attr_label = attr['long_upper']
end
table.insert( requirements, h.new_color('value', val) .. ' ' .. attr_label )
end
end
-- return early
if #requirements == 0 then
return
end
return h.new_color('default', 'Requires ') .. table.concat(requirements, ', ')
end,
},
},
-- Gem description
{
css_class = '-textwrap text-color -gemdesc',
{
args = {'gem_description'},
func = core.factory.display_value_only('gem_description'),
},
},
-- Gem Quality Stats
{
css_class = '-textwrap text-color -mod',
{
args = {'quality_stat_text'},
func = function ()
lines = {}
lines[#lines+1] = h.new_color('default', 'Per 1% Quality:')
lines[#lines+1] = g_args.quality_stat_text
return table.concat(lines, '<br>')
end,
},
},
-- Gem Implicit Stats
{
css_class = '-textwrap text-color -mod',
{
args = function ()
return core.class_groups.gems.keys[g_args.class] and g_args.stat_text
end,
func = function ()
lines = {}
lines[#lines+1] = g_args.stat_text
if g_args.gem_tags:contains('Vaal') then
lines[#lines+1] = h.new_color('corrupted', 'Corrupted')
end
return table.concat(lines, '<br>')
end,
},
},
-- Implicit Stats
{
css_class = 'text-color -mod',
func = function ()
return {g_args.implicit_stat_text}
end,
},
-- Stats
{
css_class = 'text-color -mod',
func = function ()
return {g_args.explicit_stat_text}
end,
},
-- Experience
{
{
args = {'experience'},
func = core.factory.display_value{
keys={'experience'},
options = {
[1] = {
fmt = '%i',
},
},
},
},
},
-- Description (currency, doodads)
{
css_class = '-textwrap text-color -mod',
{
args = {'description'},
func = core.factory.display_value_only('description'),
},
},
-- Variations (for doodads)
{
css_class = 'text-color -mod',
{
args = {'variation_count'},
func = function ()
local txt
if g_args.variation_count == 1 then
txt = 'Variation'
else
txt = 'Variations'
end
return string.format('%i %s', g_args.variation_count, txt)
end,
},
},
-- Flavour Text
{
css_class = '-textwrap text-color -flavour',
{
args = {'flavour_text'},
func = core.factory.display_value_only('flavour_text'),
},
},
-- Help text
{
css_class = '-textwrap text-color -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{
keys={'master_favour_cost'},
options = {
[1] = {
before = 'Favour cost: ',
color = 'currency',
},
},
},
},
},
}
core.item_link_params = {'name', 'inventory_icon', 'html'}
core.item_link_broken_cat = '[[Category:Pages with broken item links]]'
core.result = {}
core.result.generic_item = {
{
arg = 'level',
header = m_game.level_requirement.icon,
property = 'Has level requirement',
cast = tonumber,
display = h.tbl.display.na_or_val,
},
{
arg = 'ar',
header = util.html.abbr('AR', 'Armour'),
property = 'Has armour range average',
cast = nil,
display = h.tbl.display.factory.range{property='armour'},
},
{
arg = 'ev',
header = util.html.abbr('EV', 'Evasion Rating'),
property = 'Has evasion range average',
cast = nil,
display = h.tbl.display.factory.range{property='evasion'},
},
{
arg = 'es',
header = util.html.abbr('ES', 'Energy Shield'),
property = 'Has energy shield range average',
cast = nil,
display = h.tbl.display.factory.range{property='energy shield'},
},
{
arg = 'block',
header = 'Block',
property = 'Has block range average',
cast = nil,
display = h.tbl.display.factory.range{property='block'},
},
-- Todo: replace this with a proper range eventually.
{
arg = 'weapon',
header = 'Min<br>Damage',
property = 'Has minimum physical damage range average',
cast = nil,
display = h.tbl.display.factory.range{property='minimum physical damage'},
},
{
arg = 'weapon',
header = 'Max<br>Damage',
property = 'Has maximum physical damage range average',
cast = nil,
display = h.tbl.display.factory.range{property='maximum physical damage'},
},
{
arg = 'weapon',
header = util.html.abbr('APS', 'Attacks per second'),
property = 'Has attack speed range average',
cast = nil,
display = h.tbl.display.factory.range{property='attack speed'},
},
{
arg = 'weapon',
header = 'Critical',
property = 'Has critical strike chance range average',
cast = nil,
display = h.tbl.display.factory.range{property='critical strike chance'},
},
{
arg = 'stat',
header = 'Stats',
property = 'Has stat text',
cast = nil,
display = h.tbl.display.na_or_val,
},
}
for _, data in ipairs(core.dps_map) do
table.insert(core.result.generic_item, #core.result.generic_item, {
arg = data.name,
header = data.label,
property = nil,
cast = nil,
display = h.tbl.display.factory.range{property=data.property, no_base=true},
})
end
core.result.skill_gem = {
{
arg = 'icon',
header = util.html.abbr('L', 'Support gem letter.'),
property = 'Has support gem letter HTML',
cast = nil,
display = h.tbl.display.na_or_val,
},
{
arg = 'skill_icon',
header = 'Icon',
property = 'Has skill gem icon',
cast = nil,
display = h.tbl.display.wikilink,
},
{
arg = 'description',
header = 'Description',
property = 'Has description',
cast = nil,
display = h.tbl.display.na_or_val,
},
{
arg = 'level',
header = m_game.level_requirement.icon,
property = 'Has level requirement',
cast = tonumber,
display = h.tbl.display.na_or_val,
},
{
arg = 'crit',
header = util.html.abbr('Crit', 'Critical Strike Chance'),
property = 'Has critical strike chance',
cast = tonumber,
display = h.tbl.display.percent,
},
{
arg = 'cast_time',
header = util.html.abbr('Cast<br>Time', 'Casting time of the skill in seconds'),
property = 'Has cast time',
cast = tonumber,
display = h.tbl.display.seconds,
},
{
arg = 'dmgeff',
header = util.html.abbr('Dmg.<br>Eff.', 'Damage Effectiveness'),
property = 'Has damage effectiveness',
cast = tonumber,
display = h.tbl.display.percent,
},
{
arg = 'mcm',
header = util.html.abbr('MCM', 'Mana cost multiplier - missing values indicate it changes on gem level'),
property = 'Has mana multiplier',
cast = tonumber,
display = h.tbl.display.percent,
},
{
arg = 'mana',
header = util.html.abbr('Mana', 'Mana cost'),
property = 'Has mana cost',
cast = tonumber,
display = function (tr, value, data)
local appendix = ''
if data['?Has percentage mana cost'] then
appendix = appendix .. '%'
end
if data['?Has reservation mana cost'] then
appendix = appendix .. ' ' .. util.html.abbr('R', 'reserves mana')
end
local str
if value ~= nil then
str = string.format('%d', value) .. appendix
end
return h.na_or_val(tr, str)
end,
},
{
arg = 'vaal',
header = util.html.abbr('Souls', 'Vaal souls requirement in Normal/Cruel/Merciless difficulty'),
property = 'Has vaal souls requirement',
cast = tonumber,
display = function (tr, value, data)
return h.na_or_val(tr, value, function (value)
return string.format('%d / %d / %d', value, value*1.5, value*2)
end)
end,
},
{
arg = 'vaal',
header = util.html.abbr('Uses', 'Maximum number of stored uses'),
property = 'Has vaal stored uses',
cast = tonumber,
display = h.tbl.display.na_or_val,
},
{
arg = 'radius',
header = util.html.abbr('R1', 'Primary radius'),
property = 'Has primary radius',
cast = tonumber,
display = function (tr, value, data)
return h.na_or_val(tr, value, core.factory.descriptor_value{key='?Has primary radius description', tbl=data})
end,
},
{
arg = 'radius',
header = util.html.abbr('R2', 'Secondary radius'),
property = 'Has secondary radius',
cast = tonumber,
display = function (tr, value, data)
return h.na_or_val(tr, value, core.factory.descriptor_value{key='?Has secondary radius description', tbl=data})
end,
},
{
arg = 'radius',
header = util.html.abbr('R3', 'Tertiary radius'),
property = 'Has tertiary radius',
cast = tonumber,
display = function (tr, value, data)
return h.na_or_val(tr, value, core.factory.descriptor_value{key='?Has tertiary radius description', tbl=data})
end,
},
}
for _, attr in ipairs(m_game.constants.attributes) do
table.insert(core.result.generic_item, 1, {
arg = attr.short_lower,
header = attr.icon,
property = string.format('Has base %s requirement', attr.long_lower),
cast = tonumber,
display = h.tbl.display.na_or_val,
})
table.insert(core.result.skill_gem, 3, {
arg = attr.short_lower,
header = attr.icon,
property = string.format('Has %s percentage', attr.long_lower),
cast = tonumber,
display = function (tr, value)
return h.na_or_val(tr, value, function (value)
return '[[File:Yes.png|yes|link=]]'
end)
end,
})
end
-- ----------------------------------------------------------------------------
-- Page views
-- ----------------------------------------------------------------------------
--
-- Template:Item
--
function p.itembox (frame)
--
-- Args/Frame
--
local t = os.clock()
g_args = getArgs(frame, {
parentFirst = true
})
g_frame = util.misc.get_frame(frame)
--
-- Shared args
--
g_args._total_args = {}
g_args._properties = {}
-- Must validate some argument early. It is required for future things
local err
err = core.process_arguments{array=core.default_args}
if err then
return err
end
err = core.process_arguments{array=core.item_classes[g_args.class].args}
if err then
return err
end
-- set defaults
for k, v in pairs(core.item_classes[g_args.class].defaults) do
if g_args[k] == nil then
g_args[k] = v
end
end
-- Mods
g_args._mods = {}
g_args._stats = {}
g_args._subobjects = {}
g_args._properties['Has mod ids'] = {}
for _, k in ipairs({'implicit', 'mod'}) do
local success = true
local i = 1
while success do
success = core.validate_mod{key=k, i=i}
i = i + 1
end
end
-- Transpose stats into subobjects
for id, data in pairs(g_args._stats) do
g_args._subobjects[id] = {
['Has stat ids'] = id,
['Has minimum stat values'] = data.min,
['Has maximum stat values'] = data.max,
['Has average stat values'] = data.avg,
}
end
-- Handle extra stats (for gems)
if core.class_groups.gems.keys[g_args.class] then
m_skill.skill(g_frame, g_args)
end
-- base_item
-- TODO: Mods from base item...
core.map.base_item.func()
table.insert(g_args._total_args, 'base_item')
for k, data in pairs(core.stat_map) do
local value
if g_args.base_item and core.map[k] then
value = tonumber(g_args.base_item[core.map[k].property])
else
value = g_args[k]
end
if value == nil and data.default ~= nil then
value = data.default
g_args[k] = data.default
end
if value ~= nil then
value = {min=value, max=value}
-- 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 g_args._stats[statid] ~= nil then
h.stat[operator](value, g_args._stats[statid])
end
end
end
end
-- For increased stats we need to add them up first
local st = data.stats_increased
if st ~= nil then
local total_increase = {min=0, max=0}
for _, statid in ipairs(st) do
if g_args._stats[statid] ~= nil then
for var, current_value in pairs(total_increase) do
total_increase[var] = current_value + g_args._stats[statid][var]
end
end
end
h.stat.more(value, total_increase)
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
g_args._properties[data.property .. range_data.property] = value[short_key]
end
end
for short_key, range_data in pairs(h.range_map) do
g_args[k .. range_data.var] = value[short_key]
end
end
end
-- late processing
err = core.process_arguments{array=core.late_args}
if err then
return err
end
-- calculate and handle weapon dps
if core.class_groups.weapons.keys[g_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] + g_args[string.format('%s_%s%s', damage_key, var_type, range_data.var)]
end
end
end
for short_key, range_data in pairs(h.range_map) do
local result = (damage.min[short_key] + damage.max[short_key]) / 2 * g_args[string.format('attack_speed%s', range_data.var)]
g_args[string.format('%s%s', data.name, range_data.var)] = result
-- It will set the property, even if 0.
-- Not sure if it is better to not set it, but on the other hand this way it can be queried for having no dps of a particular type
g_args._properties[string.format('Has %s%s', data.property, range_data.property)] = result
end
end
end
-- Setting semantic properties Part 1 (base values)
local val
for _, k in ipairs(g_args._total_args) do
local prop = core.map[k].property
val = g_args[k]
if val == nil then
else
g_args._properties[prop] = val
end
end
util.smw.set(g_frame, g_args._properties)
-- Subobjects
local command
for key, properties in pairs(g_args._subobjects) do
command = ''
if type(key) ~= 'number' then
command = key
end
util.smw.subobject(g_frame, command, properties)
end
--
-- Validate arguments
--
local x = v.itembox()
mw.logObject(os.clock() - t)
return x
end
--
-- Template:Item link & Template:Sl
--
function p.item_link (frame)
--
-- Args/Frame
--
g_args = getArgs(frame, {
parentFirst = true,
removeBlanks = false,
})
g_frame = util.misc.get_frame(frame)
-- Backwards compability
g_args.item_name = g_args.item_name or g_args[1]
if g_args.item_name ~= nil then
g_args.item_name = string.lower(g_args.item_name)
end
g_args.name = g_args.name or g_args[2]
if util.table.has_all_value(g_args, {'page', 'item_name', 'item_name_exact'}) then
error('page, item_name or item_name_exact must be specified')
end
g_args.large = util.cast.boolean(g_args.large)
local img
local result
if util.table.has_one_value(g_args, core.item_link_params, nil) or g_args.item_name ~= nil then
local query = {}
if g_args.page ~= nil then
-- TODO returns the result even if the + format is specified.
query[#query+1] = string.format('[[%s]]', g_args.page)
else
if g_args.item_name ~= nil then
query[#query+1] = string.format('[[Has lowercase names::%s]]', g_args.item_name)
elseif g_args.item_name_exact ~= nil then
query[#query+1] = string.format('[[Has name::%s]]', g_args.item_name_exact)
end
query[#query] = query[#query] .. ' [[Has inventory icon::+]] [[Has infobox HTML::+]]'
if g_args.link_type == 'skill' then
query[#query] = query[#query] .. ' [[Concept:Skill gems]]'
end
end
query[#query+1] = '?Has name'
query[#query+1] = '?Has inventory icon'
query[#query+1] = '?Has infobox HTML'
query[#query+1] = '?Has alternate inventory icons'
query[#query+1] = '?Has inventory width'
query[#query+1] = '?Has inventory height'
-- attributes
result = util.smw.query(query, g_frame)
local err
if #result == 0 then
err = util.misc.raise_error_or_return{raise_required=true, args=g_args, msg=string.format(
'No results found for search parameter "%s".',
g_args.page or g_args.item_name or g_args.item_name_exact
)}
elseif #result > 1 then
err = util.misc.raise_error_or_return{raise_required=true, args=g_args, msg=string.format(
'Too many results for search parameter "%s". Consider using page parameter instead.',
g_args.page or g_args.item_name or g_args.item_name_exact
)}
end
if err ~= nil then
return err .. core.item_link_broken_cat
end
result = result[1]
else
result = {g_args.page or g_args.item_name_exact}
end
for _, k in ipairs(core.item_link_params) do
local prop = core.map[k].property
if g_args[k] ~= nil then
result[prop] = g_args[k]
end
end
if g_args.image ~= nil then
if result['Has alternate inventory icons'] == '' then
return util.misc.raise_error_or_return{raise_required=true, args=g_args, msg=string.format(
'Image parameter was specified, but there is no alternate art defined on page "%s"',
result[1]
) .. core.item_link_broken_cat}
end
result['Has alternate inventory icons'] = util.string.split(result['Has alternate inventory icons'], '<MANY>')
local index = tonumber(g_args.image)
if index ~= nil then
img = result['Has alternate inventory icons'][index]
else
-- offset 1 is needed
local suffix = string.len(' inventory icon.png') + 1
-- add an extra offset by 1 to account for the space
local prefix = string.len(string.sub(result['Has inventory icon'], 1, -suffix)) + 2
for _, filename in ipairs(result['Has alternate inventory icons']) do
if string.sub(filename, prefix, -suffix) == g_args.image then
img = filename
break
end
end
end
if img == nil then
return util.misc.raise_error_or_return{raise_required=true, args=g_args, msg=string.format(
'Alternate art with index/name "%s" not found on page "%s"',
g_args.image, result[1]
) .. core.item_link_broken_cat}
end
elseif result['Has inventory icon'] ~= '' then
img = result['Has inventory icon']
end
-- output
local container = mw.html.create('span')
container:attr('class', 'inline-infobox-container')
if not g_args.large then
container:wikitext(string.format('[[%s]]', img))
end
container:wikitext(string.format('[[%s|%s]]', result[1], result['Has name'] or result[1]))
if result['Has infobox HTML'] ~= '' then
container
:tag('span')
:attr('class', 'inline-infobox-hover')
:wikitext(result['Has infobox HTML'])
:wikitext(string.format('[[%s]]', img))
:done()
end
if g_args.large then
local width = tonumber(result['Has inventory width']) or tonumber(g_args.width)
local height = tonumber(result['Has inventory height']) or tonumber(g_args.height)
if width and height then
img = string.format('[[%s|%sx%spx]]', img, width*image_size, height*image_size)
elseif width then
img = string.format('[[%s|%spx]]', img, width*image_size)
elseif height then
img = string.format('[[%s|x%spx]]', img, height*image_size)
else
img = string.format('[[%s]]', img)
end
container:wikitext(img)
end
return tostring(container)
end
--
-- Template:Il
--
function p.item_link_compability (frame)
if util.misc.is_frame(frame) then
frame.args.raise = true
else
frame.raise = true
end
local result
local status, err = pcall(function ()
result = p.item_link(frame)
end)
mw.logObject(err)
if status then
return result
elseif string.match(err, ".*Too many results.*") then
return err .. core.item_link_broken_cat
else
return require('Module:Item').itemLink(frame)
end
end
-- ----------------------------------------------------------------------------
-- Result formatting templates for SMW queries
-- ----------------------------------------------------------------------------
--
-- Template:
--
function p.simple_item_list_row(frame)
-- Args
g_args = getArgs(frame, {
parentFirst = true
})
g_frame = util.misc.get_frame(frame)
--
local args = util.string.split_args(g_args.userparam, {sep=', '})
g_args.userparam = args
local link = p.item_link{page=g_args[1], name=g_args['?Has name'], inventory_icon=g_args['?Has inventory icon'] or '', html=g_args['?Has infobox HTML'] or ''}
if args.format == nil then
return '* ' .. link
elseif args.format == 'none' then
return link
elseif args.format == 'li' then
return string.format('<li>%s</li>', link)
else
error(string.format('Unrecognized format parameter "%s"', args.format))
end
end
-- ----------------------------------------------------------------------------
-- Reponsibile for subtemplates of Template:SMW item table
--
item_table_factory = {}
function item_table_factory.intro(args)
-- args:
-- data_array
-- header
return function (frame)
-- Args
local tpl_args = getArgs(frame, {
parentFirst = true
})
g_frame = util.misc.get_frame(frame)
--
tpl_args.userparam = 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">' .. tostring(tr)
end
end
function item_table_factory.row(args)
-- args:
-- data_array
return function (frame)
-- Args
local tpl_args = getArgs(frame, {
parentFirst = true
})
g_frame = util.misc.get_frame(frame)
--
tpl_args.userparam = util.string.split_args(tpl_args.userparam, {sep=', '})
local tr = mw.html.create('tr')
local il_args = {
page=tpl_args[1],
name=tpl_args['?Has name'],
inventory_icon=tpl_args['?Has inventory icon'],
html=tpl_args['?Has infobox HTML'],
width=tpl_args['?Has inventory width'],
height=tpl_args['?Has inventory height'],
}
if tpl_args.userparam.large then
il_args.large = tpl_args.userparam.large
end
tr
:tag('td')
:wikitext(p.item_link(il_args))
:done()
for _, rowinfo in ipairs(args.data_array) do
if rowinfo.arg == nil or tpl_args.userparam[rowinfo.arg] then
local value
if rowinfo.property ~= nil then
value = tpl_args['?' .. rowinfo.property]
if rowinfo.cast then
value = rowinfo.cast(value)
end
end
rowinfo.display(tr, value, tpl_args)
end
end
return tostring(tr)
end
end
-- Template:SMW item table/skill_gem/intro
p.skill_gem_list_intro = item_table_factory.intro{data_array=core.result.skill_gem, header='Skill gem'}
-- Template:SMW item table/skill gem
p.skill_gem_list_row = item_table_factory.row{data_array=core.result.skill_gem}
-- Template:SMW item table/intro
p.item_list_intro = item_table_factory.intro{data_array=core.result.generic_item, header='Item'}
-- Template:SMW item table
p.item_list_row = item_table_factory.row{data_array=core.result.generic_item}
-- ----------------------------------------------------------------------------
-- Item lists
-- ----------------------------------------------------------------------------
function p.skill_gem_list_by_gem_tag(frame)
-- Args
g_args = getArgs(frame, {
parentFirst = true
})
g_frame = util.misc.get_frame(frame)
if g_args.class == 'Support Skill Gems' then
elseif g_args.class == 'Active Skill Gems' then
else
error('invalid item class')
end
local query = {}
query[#query+1] = string.format('[[Has item class::%s]]', g_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 = util.smw.query(query, g_frame)
local tags = {}
for _, row in ipairs(results) do
row['Has gem tags'] = 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(p.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
-- ----------------------------------------------------------------------------
-- Property views..
-- ----------------------------------------------------------------------------
function v.itembox ()
--todo: remove g_args.frame ?
local container = v._itembox_core()
if g_args.gem_icon ~= nil then
container:wikitext(string.format('[[%s]]', g_args.gem_icon))
end
-- Store the infobox so it can be accessed with ease on other pages
g_frame:callParserFunction('#set:', {['Has infobox HTML'] = tostring(container)})
if g_args.inventory_icon ~= nil then
container:wikitext(string.format('[[%s]]', g_args.inventory_icon))
end
local infobox = tostring(container)
local span = mw.html.create('span')
span
:attr('class', 'infobox-page-container')
:wikitext(infobox)
if g_args.skill_screenshot then
span:wikitext(string.format('<br>[[%s|300px]]', g_args.skill_screenshot))
end
return tostring(span) .. v._itembox_categories()
end
function v._itembox_core()
-- needed later for setting caculated properties
g_args._properties = {}
local frame_css = {
['Currency'] = 'currency',
['Stackable Currency'] = 'currency',
['Microtransactions'] = 'currency',
['Hideout Doodads'] = 'currency',
['Active Skill Gems'] = 'gem',
['Support Skill Gems'] = 'gem',
['Quest'] = 'quest',
['Divination Card'] = 'divicard'
}
local container = mw.html.create('span')
:attr( 'class', 'item-box -' .. ( string.lower(g_args.frame or frame_css[g_args.class] or g_args.rarity)))
if g_args.class == 'Divination Card' then
--TODO div card code
else
container
:tag('span')
:attr( 'class', 'header -' .. (g_args.base_item and 'double' or 'single') )
:wikitext( g_args.name .. (g_args.base_item and ' <br> ' .. g_args.base_item or '') )
:done()
local grpcont
local valid
local statcont = container:tag('span')
statcont
:attr('class', 'item-stats')
:done()
for _, group in ipairs(core.display_groups) 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 g_args[key] == nil then
valid = false
break
end
end
elseif type(disp.args) == 'function' then
valid = disp.args()
end
if valid then
grpcont[#grpcont+1] = disp.func()
end
end
else
grpcont = group.func()
end
if #grpcont > 0 then
statcont
:tag('span')
:attr('class', 'group ' .. (group.css_class or ''))
:wikitext(table.concat(grpcont, '<br>'))
:done()
end
end
end
g_frame:callParserFunction('#set:', g_args._properties)
return container
end
function v._itembox_categories()
cats = {}
cats[#cats+1] = g_args.class
if g_args.rarity == 'Unique' then
cats[#cats+1] = 'Unique ' .. g_args.class
end
for _, attr in ipairs(m_game.constants.attributes) do
if g_args[attr.long_lower .. '_percent'] then
cats[#cats+1] = string.format('%s %s', attr.long_upper, g_args.class)
end
end
local suffix
if g_args.class == 'Active Skill Gems' or g_args.class == 'Support Skill Gems' then
suffix = ' (gem tag)'
end
if suffix ~= nil then
for _, tag in ipairs(g_args.gem_tags) do
cats[#cats+1] = tag .. suffix
end
end
if #g_args.alternate_art_inventory_icons > 0 then
cats[#cats+1] = 'Items with alternate artwork'
end
-- TODO: add maintenance categories
--
-- Output formatting
--
return util.misc.add_category(cats)
end
return p