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:
-- 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
-- Maps: Area level can be retrived eventually
-- Essence: Essence type prop/colunm
-- ----------------------------------------------------------------------------
-- 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 c = {}
c.image_size = 39
-- 9 doesn't hit the query limit
c.max_mod_params = 11
c.max_stat_params = 8
-- ----------------------------------------------------------------------------
-- Other stuff
-- ----------------------------------------------------------------------------
local h = {}
function h.debug(func)
if g_args.debug == nil then
return
end
func()
end
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',
},
}
function h.stats_update(id, value, modid)
if g_args._stats[id] == nil then
value.references = {modid}
g_args._stats[id] = value
else
if modid ~= nil then
table.insert(g_args._stats[id].references, mod_data.result['Is Mod'])
end
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
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)
-- args:
-- key - implict or explicit
-- i
-- value
local value = g_args[args.key .. args.i]
local out = {
result=nil,
modid=nil,
type=args.key,
}
if value ~= nil then
table.insert(g_args.mods, value)
table.insert(g_args[args.key .. '_mods'], value)
out.modid = value
--out.result = nil
table.insert(g_args._mods, out)
return true
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.result = value
table.insert(g_args._mods, out)
return true
end
end
return false
end
function core.process_smw_mods()
if #g_args.mods == 0 then
return
end
local mods = {}
for _, mod_data in ipairs(g_args._mods) do
if mod_data.result == nil then
mods[mod_data.modid] = mod_data
end
end
local results = {}
local result
local query
local number_of_queries = math.ceil(#g_args.mods / c.max_mod_params)
local pages = {}
for query_number=1, number_of_queries do
local offset = (query_number-1)*c.max_mod_params
local mod_ids = {}
for j=(1+offset), (c.max_mod_params+offset) do
mod_ids[#mod_ids+1] = g_args.mods[j]
end
query = {
string.format('[[Is mod::%s]]', table.concat(mod_ids, '||')),
'?#=Page',
'?Is mod#',
'?Has mod group#',
'?Has mod type#',
'?Has stat text#',
'?Has level requirement#',
}
result = util.smw.query(query, g_frame)
-- remap this as table with modids as key
for _, row in ipairs(result) do
results[#results+1] = row
local mod_data = mods[row['Is mod']]
mod_data.result = row
-- needed for the query
pages[#pages+1] = row[1]
-- needed for the mapping stats to mods
pages[row[1]] = mod_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(row['Has level requirement']) * 0.8)
if req > g_args[key] then
g_args[key] = req
end
end
end
end
-- TODO: Can items have mods twice? I dont think so, if they do it would need to be accounted for here
if #results ~= #g_args.mods then
local missing = {}
for _, modid in ipairs(g_args.mods) do
if mods[modid].result == nil then
missing[#missing+1] = modid
end
end
local duplicates = {}
for _, row in ipairs(results) do
if duplicates[row['Is mod']] == nil then
duplicates[row['Is mod']] = {row['Page']}
else
table.insert(duplicates[row['Is mod']], row['Page'])
end
end
local text = {}
for modid, pages in pairs(duplicates) do
if #pages <= 1 then
duplicates[modid] = nil
else
text[#text+1] = string.format('%s (pages: %s)', modid, table.concat(pages, ', '))
end
end
error(string.format('Number of mods found does not match number of queried mods. Not found: "%s", duplicates: "%s"', table.concat(missing, ', '), table.concat(text)))
end
-- fetch stats
number_of_queries = math.ceil(#pages / c.max_stat_params)
for query_number=1, number_of_queries do
local offset = (query_number-1)*c.max_stat_params
local temp_pages = {}
for j=(1+offset), (c.max_stat_params+offset) do
temp_pages[#temp_pages+1] = pages[j]
end
query = {
string.format('[[-Has subobject::%s]] [[Has stat id::+]] [[Has minimum stat value::+]] [[Has maximum stat value::+]]', table.concat(temp_pages, '||')),
'?Is stat number#',
'?Has stat id#',
'?Has minimum stat value#',
'?Has maximum stat value#',
}
local stats = util.smw.query(query, g_frame)
-- process and cache stats
for _, stat in ipairs(stats) do
local mod_data = pages[util.string.split(stat[1], '#')[1]]
if mod_data.result.stats == nil then
mod_data.result.stats = {stat, }
else
mod_data.result.stats[#mod_data.result.stats+1] = stat
end
local id = stat['Has stat id']
local value = {
min = tonumber(stat['Has minimum stat value']),
max = tonumber(stat['Has maximum stat value']),
}
value.avg = (value.min+value.max)/2
h.stats_update(id, value, mod_data.result['Is Mod'])
end
end
end
function core.process_base_item(args)
local query = {}
if g_args.base_item_id ~= nil then
query[#query+1] = string.format('[[Has metadata id::%s]]', g_args.base_item_id)
elseif g_args.base_item_page ~= nil then
query[#query+1] = string.format('[[%s]]', g_args.base_item_page)
elseif g_args.base_item ~= nil then
query[#query+1] = string.format('[[Has name::%s]]', g_args.base_item)
elseif g_args.rarity ~= 'Normal' then
error(core.err{msg='Rarity is set to above normal, but base item is not set. A base item for rarities above normal is required!'})
else
return
end
if #query > 1 and g_args.rarity == 'Normal' then
error(core.err{msg='Base item parameter is set, but rarity is set to normal. A rarity above normal is required!'})
end
query[#query+1] = string.format('[[Has item class::%s]]', g_args['class'])
query[#query+1] = '[[Has rarity::Normal]]'
query[#query+1] = '?Has implicit mod ids#'
query[#query+1] = '?Has metadata id'
query[#query+1] = '?Has name'
for _, k in ipairs(g_args._base_item_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. Consider using base_item_page or base_item_id to narrow down the results.'})
-- TODO be more explicit in the error?
end
result = result[1]
--Copy values..
for _, k in ipairs(g_args._base_item_args) do
local data = core.map[k]
local value = result[data.property]
-- I can just use data.default since it will be nil if not provided. Neat! ;)
if value ~= "" and g_args[k] == data.default then
g_args[k] = value
if data.property_func ~= nil then
data.property_func()
elseif data.func ~= nil then
data.func()
end
elseif value == "" then
h.debug(function ()
mw.logObject(string.format("Base item property not found: %s", data.property))
end)
elseif g_args[k] ~= data.default then
h.debug(function ()
mw.logObject(string.format("Value for arg '%s' is set to something else then default: %s", k, tostring(g_args[k])))
end)
end
end
g_args.base_item_data = result
core.process_arguments{array={'base_item', 'base_item_page', 'base_item_id'}}
end
function core.process_arguments(args)
for _, k in ipairs(args.array) do
local data = core.map[k]
table.insert(g_args._total_args, k)
if data.no_copy == nil then
table.insert(g_args._base_item_args, k)
end
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
if data.default ~= nil and g_args[k] == nil then
g_args[k] = data.default
end
end
end
function core.process_mod_stats(args)
local lines = {}
local skip = core.class_specifics[g_args.class]
if skip then
skip = skip.skip_stat_lines
end
for _, modinfo in ipairs(g_args._mods) do
if modinfo.type == args.type then
if modinfo.modid == nil then
table.insert(lines, modinfo.result)
else
for _, line in ipairs(util.string.split(modinfo.result['Has stat text'], '<br>')) do
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
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)
return function ()
g_args[k] = tonumber(g_args[k])
end
end
function core.factory.boolean_cast(k)
return function()
if g_args[k] ~= nil then
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:
-- type: Type of the keys (nil = regular, gem = skill gems, stat = stats)
-- 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({options = {}}) do
if args[k] == nil then
args[k] = default
end
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, data in ipairs(args.options) do
local value = g_args['static_' .. 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=g_args[string.format('level1_%s', data.key)],
max=g_args[string.format('level%s_%s', g_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 = g_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] = g_args[data.key]
local value = {}
if g_args[data.key .. '_range_minimum'] ~= nil then
value.min = g_args[data.key .. '_range_minimum']
value.max = g_args[data.key .. '_range_maximum']
elseif g_args[data.key] ~= nil then
value.min = g_args[data.key]
value.max = g_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 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
--
-- format:
-- g_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,
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 sep = ''
if g_args.implicit_stat_text and g_args.explicit_stat_text then
sep = string.format('<span class="item-stat-separator -%s"></span>', g_args.frame_type)
end
local text = (g_args.implicit_stat_text or '') .. sep .. (g_args.explicit_stat_text or '')
if string.len(text) > 0 then
g_args.stat_text = text
end
end,
},
-- generic
class = {
no_copy = true,
property = 'Has item class',
func = core.factory.table_cast('class', {key='full', tbl=m_game.constants.item.class}),
},
rarity = {
no_copy = true,
property = 'Has rarity',
func = core.factory.table_cast('rarity', {key={'full', 'long_lower'}, tbl=m_game.constants.item.rarity, rtrkey='full'}),
},
name = {
no_copy = true,
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'),
default = true,
},
drop_level = {
no_copy = true,
property = 'Has drop level',
func = core.factory.number_cast('drop_level'),
},
drop_level_maximum = {
no_copy = true,
property = 'Has maximum drop level',
func = core.factory.number_cast('drop_level_maximum'),
},
required_level = {
property = 'Has base level requirement',
func = core.factory.number_cast('required_level'),
default = 1,
},
required_level_final = {
property = 'Has level requirement',
func = function ()
g_args.required_level_final = g_args.required_level
end,
default = 1,
},
required_dexterity = {
property = 'Has base dexterity requirement',
func = core.factory.number_cast('required_dexterity'),
default = 0,
},
required_strength = {
property = 'Has base strength requirement',
func = core.factory.number_cast('required_strength'),
default = 0,
},
required_intelligence = {
property = 'Has base intelligence requirement',
func = core.factory.number_cast('required_intelligence'),
default = 0,
},
inventory_icon = {
no_copy = true,
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 = {
no_copy = true,
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,
default = {},
},
buff_icon = {
property = 'Has buff icon',
func = function()
g_args.buff_icon = string.format('File:%s status icon.png', g_args.name)
end,
},
help_text = {
property = 'Has help text',
func = nil,
},
flavour_text = {
no_copy = true,
property = 'Has flavour text',
func = nil,
},
tags = {
property = 'Has tags',
property_func = function ()
g_args.tags = util.string.split(g_args.tags, '<MANY>')
end,
func = core.factory.array_table_cast('tags', {
tbl = m_game.constants.tags,
errmsg = '%s is not a valid tag',
}),
},
metadata_id = {
no_copy = true,
property = 'Has metadata id',
func = nil,
},
--
-- 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('charges_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'),
default = 0,
},
energy_shield = {
property = 'Has base energy shield',
func = core.factory.number_cast('energy_shield'),
default = 0,
},
evasion = {
property = 'Has base evasion',
func = core.factory.number_cast('evasion'),
default = 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',
-- TODO: default rework
func = core.factory.array_table_cast('gem_tags', {
tbl = m_game.constants.item.gem_tags,
errmsg = '%s is not a valid tag',
}),
default = {},
},
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 = 'Has map tier',
func = core.factory.number_cast('tier'),
},
map_guild_character = {
property = 'Has map guild character',
func = nil,
},
map_area_id = {
property = 'Has map area id',
func = nil, -- TODO: Validate against a query?
},
map_area_level = {
property = 'Has map area level',
func = core.factory.number_cast('map_area_level'),
},
unique_map_guild_character = {
property = 'Has unqiue map guild character',
func = nil,
},
unique_map_area_id = {
property = 'Has unique map area id',
func = nil, -- TODO: Validate against a query?
},
unique_map_area_level = {
property = 'Has unique map area level',
func = core.factory.number_cast('unique_map_area_level'),
},
--
-- 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,
},
-- for essences
is_essence = {
property = 'Is essence',
func = core.factory.boolean_cast('is_essence'),
default = false,
},
essence_level_restriction = {
property = 'Has essence level restriction',
func = core.factory.number_cast('essence_level_restriction'),
},
essence_tier = {
property = 'Has essence tier',
func = core.factory.number_cast('essence_tier'),
},
--
-- 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 variation count',
func = core.factory.number_cast('variation_count'),
},
-- Propehcy
prophecy_id = {
property = 'Has prophecy id',
func = nil,
},
prediction_text = {
property = 'Has prophecy prediction text',
func = nil,
},
seal_cost_normal = {
property = 'Has prophecy seal cost in normal difficulty',
func = core.factory.number_cast('seal_cost_normal'),
},
seal_cost_cruel = {
property = 'Has prophecy seal cost in cruel difficulty',
func = core.factory.number_cast('seal_cost_cruel'),
},
seal_cost_merciless = {
property = 'Has prophecy seal cost in merciless difficulty',
func = core.factory.number_cast('seal_cost_merciless'),
},
-- ------------------------------------------------------------------------
-- derived stats
-- ------------------------------------------------------------------------
-- For rarity != normal, rarity already verified
base_item = {
no_copy = true,
property = 'Has base item',
func = function ()
g_args.base_item = g_args.base_item_data['Has name']
end,
},
base_item_id = {
no_copy = true,
property = 'Has base item metadata id',
func = function()
g_args.base_item_id = g_args.base_item_data['Has metadata id']
end,
},
base_item_page = {
no_copy = true,
property = 'Has base item wiki page',
func = function()
g_args.base_item_page = g_args.base_item_data[1]
end,
},
name_list = {
no_copy = true,
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 = {
no_copy = true,
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 = {
no_copy = true,
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,
},
frame_type = {
no_copy = true,
property = nil,
func = function ()
if g_args.name == 'Prophecy' or g_args.base_item == 'Prophecy' then
g_args.frame_type = 'prophecy'
return
end
local var = core.class_specifics[g_args.class]
if var ~= nil and var.frame_type ~= nil then
g_args.frame_type = var.frame_type
return
end
g_args.frame_type = string.lower(g_args.rarity)
end,
},
--
-- args populated by mod validation
--
mods = {
no_copy = true,
property = 'Has mod ids',
default = {},
},
implicit_mods = {
property = 'Has implicit mod ids',
property_func = function ()
g_args.implicit_mods = util.string.split(g_args.implicit_mods, '<MANY>')
for _, modid in ipairs(g_args.implicit_mods) do
g_args.mods[#g_args.mods+1] = modid
g_args._mods[#g_args._mods+1] = {
result=nil,
modid=modid,
type='implicit',
}
end
end,
default = {},
},
explicit_mods = {
no_copy = true,
property = 'Has explicit mod ids',
default = {},
},
}
core.stat_map = {
range = {
property = 'Has weapon range',
stats_add = {
'local_weapon_range_+',
},
minimum = 0,
},
damage_min = {
property = 'Has minimum physical damage',
stats_add = {
'local_minimum_added_physical_damage',
},
stats_increased = {
'local_physical_damage_+%',
},
minimum = 0,
},
damage_max = {
property = 'Has maximum physical damage',
stats_add = {
'local_maximum_added_physical_damage',
},
stats_increased = {
'local_physical_damage_+%',
},
minimum = 0,
},
fire_damage_min = {
default = 0,
property = 'Has minimum fire damage',
stats_add = {
'local_minimum_added_fire_damage',
},
minimum = 0,
},
fire_damage_max = {
default = 0,
property = 'Has maximum fire damage',
stats_add = {
'local_maximum_added_fire_damage',
},
minimum = 0,
},
cold_damage_min = {
default = 0,
property = 'Has minimum cold damage',
stats_add = {
'local_minimum_added_cold_damage',
},
minimum = 0,
},
cold_damage_max = {
default = 0,
property = 'Has maximum cold damage',
stats_add = {
'local_maximum_added_cold_damage',
},
minimum = 0,
},
lightning_damage_min = {
default = 0,
property = 'Has minimum lightning damage',
stats_add = {
'local_minimum_added_lightning_damage',
},
minimum = 0,
},
lightning_damage_max = {
default = 0,
property = 'Has maximum lightning damage',
stats_add = {
'local_maximum_added_lightning_damage',
},
minimum = 0,
},
chaos_damage_min = {
default = 0,
property = 'Has minimum chaos damage',
stats_add = {
'local_minimum_added_chaos_damage',
},
minimum = 0,
},
chaos_damage_max = {
default = 0,
property = 'Has maximum chaos damage',
stats_add = {
'local_maximum_added_chaos_damage',
},
minimum = 0,
},
critical_strike_chance = {
property = 'Has critical strike chance',
stats_add = {
'local_critical_strike_chance',
},
stats_increased = {
'local_critical_strike_chance_+%',
},
minimum = 0,
},
attack_speed = {
property = 'Has attack speed',
stats_increased = {
'local_attack_speed_+%',
},
minimum = 0,
},
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 life 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_+%',
},
minimum = 0,
},
charges_per_use = {
property = 'Has flask charges per use',
stats_increased = {
'local_charges_used_+%',
},
minimum = 0,
},
charges_max = {
property = 'Has maximum flask charges',
stats_add = {
'local_extra_max_charges',
},
stats_increased = {
'local_max_charges_+%',
'local_charges_added_+%',
},
minimum = 0,
},
block = {
property = 'Has block',
stats_add = {
'local_additional_block_chance_%',
},
minimum = 0,
},
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_+%',
},
minimum = 0,
},
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_+%',
},
minimum = 0,
},
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_+%',
},
minimum = 0,
},
required_dexterity = {
property = 'Has dexterity requirement',
stats_add = {
'local_dexterity_requirement_+'
},
stats_increased = {
'local_attribute_requirements_+%',
},
minimum = 0,
},
required_intelligence = {
property = 'Has intelligence requirement',
stats_add = {
'local_intelligence_requirement_+'
},
stats_increased = {
'local_attribute_requirements_+%',
},
minimum = 0,
},
required_strength = {
property = 'Has strength requirement',
stats_add = {
'local_strength_requirement_+'
},
stats_increased = {
'local_attribute_requirements_+%',
},
minimum = 0,
},
}
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. physical/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. physical/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', 'drop_level_maximum', 'required_level', 'required_level_final', 'inventory_icon', 'alternate_art_inventory_icons', 'flavour_text', 'help_text', 'tags', 'metadata_id', 'mods', 'implicit_mods', 'explicit_mods'}
-- 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_normal', 'seal_cost_cruel', 'seal_cost_merciless'}
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,
},
frame_type = 'gem',
},
['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,
},
frame_type = 'gem',
},
['Shields'] = {
args = {'block'},
},
['Maps'] = {
args = {'map_tier', 'map_guild_character', 'map_area_id', 'map_area_level', 'unique_map_area_id', 'unique_map_area_level', 'unique_map_guild_character'},
},
['Currency'] = {
frame_type = 'currency',
},
['Stackable Currency'] = {
args = {'is_essence', 'essence_level_restriction', 'essence_tier'},
frame_type = 'currency',
},
['Microtransactions'] = {
frame_type = 'currency',
},
['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.',
},
frame_type = 'currency',
},
['Jewel'] = {
defaults = {
help_text = 'Place into an allocated Jewel Socket on the Passive Skill Tree. Right click to remove from the Socket.',
},
skip_stat_lines = {
'Limited to %d+ %(Hidden%)',
'Jewel has a radius of %d+ %(Hidden%)',
},
},
['Quest Items'] = {
frame_type = 'quest',
},
['Divination Card'] = {
frame_type = 'divicard',
},
['Labyrinth Item'] = {
frame_type = 'currency',
},
['Labyrinth Trinket'] = {
args = {'description', 'buff_icon'},
frame_type = 'currency',
},
}
-- 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 = {'cosmetic_type'},
func = core.factory.display_value{
options = {
[1] = {
key = 'cosmetic_type',
fmt = '%s',
color = 'default'
},
},
},
},
{
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{
options = {
[1] = {
key = 'class',
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{
options = {
[1] = {
key = 'support_gem_letter_html',
before = 'Icon: ',
},
},
},
},
{
args = {'radius'},
func = core.factory.display_value{
options = {
[1] = {
key = 'radius',
before = 'Radius: ',
func = core.factory.descriptor_value{key='radius_description'},
},
[2] = {
key = 'radius_seceondary',
before = ' / ',
func = core.factory.descriptor_value{key='radius_secondary_description'},
},
[3] = {
key = 'radius_tertiary',
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{
type='gem',
options = {
[1] = {
key = 'mana_cost',
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{
type='gem',
options = {
[1] = {
key = 'mana_multiplier',
hide_default = 100,
fmt = '%i%%',
before = 'Mana Multiplier: ',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'vaal_souls_requirement',
hide_default = 0,
fmt = '%i (N) / ',
before = 'Souls per use: ',
},
[2] = {
key = 'vaal_souls_requirement',
hide_default = 0,
fmt = '%i (C) / ',
func = function (value)
return value*1.5
end,
},
[3] = {
key = 'vaal_souls_requirement',
hide_default = 0,
fmt = '%i (M)',
func = function (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',
before = 'Can store ',
after = ' use(s)',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'stored_uses',
hide_default = 0,
fmt = '%i',
before = 'Can store ',
after = ' use(s)',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'cooldown',
hide_default = 0,
fmt = '%.2f sec',
before = 'Cooldown Time: ',
},
},
},
},
{
args = {'cast_time'},
func = core.factory.display_value{
options = {
[1] = {
key = 'cast_time',
hide_default = 0,
fmt = '%.2f sec',
before = 'Cast Time: ',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'critical_strike_chance',
hide_default = 0,
fmt = '%.2f%%',
before = 'Critical Strike Chance: ',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'damage_effectiveness',
hide_default = 100,
fmt = '%i%%',
before = 'Damage Effectiveness: ',
},
},
},
},
{
args = {'projectile_speed'},
func = core.factory.display_value{
options = {
[1] = {
key = 'projectile_speed',
before = 'Projectile Speed: ',
},
},
},
},
-- Weapon only
{
args = {'damage_min', 'damage_max'},
func = core.factory.display_value{
options = {
[1] = {
key = 'damage_min',
hide_default = 0,
fmt = '%i',
after = '–',
before = 'Physical Damage: '
},
[2] = {
key = 'damage_max',
hide_default = 0,
fmt = '%i',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
before='Elemental Damage: ',
options = {
[1] = {
key = 'fire_damage_min',
hide_default = 0,
fmt = '%i',
after = h.new_color('fire', '–'),
color = 'fire',
},
[2] = {
key = 'fire_damage_max',
hide_default = 0,
fmt = '%i',
after = ' ',
color = 'fire',
},
[3] = {
key = 'cold_damage_min',
hide_default = 0,
fmt = '%i',
after = h.new_color('cold', '–'),
color = 'cold',
},
[4] = {
key = 'cold_damage_max',
hide_default = 0,
fmt = '%i',
after = ' ',
color = 'cold',
},
[5] = {
key = 'lightning_damage_min',
hide_default = 0,
fmt = '%i',
after = h.new_color('lightning', '–'),
color = 'lightning',
},
[6] = {
key = 'lightning_damage_max',
hide_default = 0,
fmt = '%i',
color = 'lightning',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
before='Chaos Damage: ',
options = {
[1] = {
key = 'chaos_damage_min',
hide_default = 0,
fmt = '%i',
after = h.new_color('chaos', '–'),
color = 'chaos',
},
[2] = {
key = 'chaos_damage_max',
hide_default = 0,
fmt='%i',
color = 'chaos',
},
},
},
},
{
args = {'critical_strike_chance'},
func = core.factory.display_value{
options = {
[1] = {
key = 'critical_strike_chance',
fmt = '%.2f%%',
before = 'Critical Strike Chance: ',
},
},
},
},
{
args = {'attack_speed'},
func = core.factory.display_value{
options = {
[1] = {
key = 'attack_speed',
fmt = '%.2f',
before = 'Attacks per Second: ',
},
},
},
},
-- Map only
{
args = {'map_area_level'},
func = core.factory.display_value{
options = {
[1] = {
key = 'map_area_level',
fmt = '%i',
before = 'Map Level: ',
},
},
},
},
{
args = {'map_tier'},
func = core.factory.display_value{
options = {
[1] = {
key = 'map_tier',
fmt = '%i',
before = 'Map Tier: ',
},
},
},
},
{
args = {'map_guild_character'},
func = core.factory.display_value{
options = {
[1] = {
key = 'map_guild_character',
fmt = '%s',
before = util.html.abbr('Guild Character', 'When used in guild creation, this map can be used for the listed character') .. ': ',
},
},
},
},
--[[{
args = {'map_item_quantity'},
func = core.factory.display_value{
key = 'map_item_quantity',
options = {
[1] = {
fmt = '%i',
before = 'Item Quantity: ',
},
},
},
},
{
args = {'map_item_rarity'},
func = core.factory.display_value{
key = 'map_item_rarity',
options = {
[1] = {
fmt = '%i',
before = 'Item Rarity: ',
},
},
},
},
{
args = {'map_pack_size'},
func = core.factory.display_value{
key = 'map_pack_size',
options = {
[1] = {
fmt = '%i',
before = 'Pack Size: ',
},
},
},
},]]--
-- Jewel Only
{
args = nil,
func = core.factory.display_value{
type = 'stat',
options = {
[1] = {
keys = {'local_unique_item_limit'},
fmt = '%i',
before = 'Limited to: ',
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type = 'stat',
options = {
[1] = {
keys = {'local_jewel_effect_base_radius'},
fmt = '%s',
func = function(value)
return string.format('%s (%i)', (m_game.constants.item.jewel_radius_to_size[value] or '?'), value)
end,
before = 'Radius: ',
},
},
},
},
-- Flask only
{
args = {'flask_mana', 'flask_duration'},
--func = core.factory.display_flask('flask_mana'),
func = core.factory.display_value{
options = {
[1] = {
key = 'flask_mana',
fmt = '%i',
before = 'Recovers ',
},
[2] = {
key = 'flask_duration',
fmt = '%.2f',
before = ' Mana over ',
after = ' seconds',
},
}
},
},
{
args = {'flask_life', 'flask_duration'},
func = core.factory.display_value{
options = {
[1] = {
key = 'flask_life',
fmt = '%i',
before = 'Recovers ',
},
[2] = {
key = 'flask_duration',
fmt = '%.2f',
before = ' Life 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{
options = {
[1] = {
key = 'flask_duration',
before = 'Lasts ',
after = ' Seconds',
fmt = '%.2f',
},
},
},
},
{
args = {'charges_per_use', 'charges_max'},
func = core.factory.display_value{
options = {
[1] = {
key = 'charges_per_use',
before = 'Consumes ',
fmt = '%i',
},
[2] = {
key = 'charges_max',
before = ' of ',
after = ' Charges on use',
fmt = '%i',
},
},
},
},
-- armor
{
args = {'block'},
func = core.factory.display_value{
options = {
[1] = {
key = 'block',
before = 'Block: ',
fmt = '%i%%',
hide_default = 0,
},
},
},
},
{
args = {'armour'},
func = core.factory.display_value{
options = {
[1] = {
key = 'armour',
before = 'Armour: ',
fmt = '%i',
hide_default = 0,
},
},
},
},
{
args = {'evasion'},
func = core.factory.display_value{
options = {
[1] = {
key = 'evasion',
before = 'Evasion: ',
fmt = '%i',
hide_default = 0,
},
},
},
},
{
args = {'energy_shield'},
func = core.factory.display_value{
options = {
[1] = {
key = 'energy_shield',
before = 'Energy Shield: ',
fmt = '%i',
hide_default = 0,
},
},
},
},
-- Misc
{
args = {'stack_size'},
func = core.factory.display_value{
options = {
[1] = {
key = 'stack_size',
hide_default = 1,
fmt = '%i',
before = 'Stack Size: ',
},
},
},
},
-- Essence stuff
{
args = {'essence_tier'},
func = core.factory.display_value{
options = {
[1] = {
key = 'essence_tier',
fmt = '%i',
before = 'Essence Tier: ',
},
},
},
},
},
-- 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 = {'drop_enabled'},
func = core.factory.display_value{
options = {
[1] = {
key = 'drop_level',
fmt = '%i',
before = 'Drop Level: ',
},
[2] = {
key = 'drop_level_maximum',
hide_default = 100,
fmt = '%i',
before = ' / ',
},
},
},
},
{
args = nil,
func = function ()
local opt = {
[1] = {
key = 'required_level_final',
hide_default = 1,
before = 'Level ',
},
}
for _, attr in ipairs(m_game.constants.attributes) do
opt[#opt+1] = {
key = 'required_' .. attr['long_lower'],
hide_default = 0,
before = ', ',
after = ' ' .. attr['short_upper'],
}
end
local requirements = core.factory.display_value{options = opt}()
-- return early
if requirements == nil then
return
end
requirements = string.gsub(requirements, '^, ', '')
return h.new_color('default', 'Requires ') .. 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 ()
if g_args.implicit_stat_text ~= '' then
return {g_args.implicit_stat_text}
else
return {}
end
end,
},
-- Stats
{
css_class = 'text-color -mod',
func = function ()
if g_args.explicit_stat_text ~= '' then
return {g_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 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'),
},
},
-- Prophecy text
{
css_class = '-textwrap text-color -value',
{
args = {'prediction_text'},
func = core.factory.display_value_only('prediction_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{
options = {
[1] = {
key = 'master_favour_cost',
before = 'Favour cost: ',
color = 'currency',
},
},
},
},
{
args = {'seal_cost_normal', 'seal_cost_cruel', 'seal_cost_merciless'},
func = core.factory.display_value{
options = {
[1] = {
key = 'seal_cost_normal',
before = 'Seal cost: <br>',
fmt = '%dx/',
color = 'currency',
},
[2] = {
key = 'seal_cost_cruel',
fmt = '%dx/',
color = 'currency',
},
[3] = {
key = 'seal_cost_merciless',
fmt = '%dx',
color = 'currency',
after = function ()
-- direct call will mess up g_args
return g_frame:expandTemplate{title='Item link',args={item_name_exact='Silver Coin'}}
end,
},
},
},
},
},
}
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 = 'essence',
header = 'Essence<br>Tier',
property = 'Has essence tier',
cast = tonumber,
display = h.tbl.display.na_or_val,
},
{
arg = 'drop_level',
header = 'Drop<br>Level',
property = 'Has drop level',
cast = tonumber,
display = h.tbl.display.na_or_val,
},
{
arg = 'stack_size',
header = 'Stack<br>Size',
property = 'Has stack size',
cast = tonumber,
display = h.tbl.display.na_or_val,
},
{
arg = 'stack_size_currency_tab',
header = util.html.abbr('Tab<br>Stack<br>Size', 'Stack size in the currency stash tab'),
property = 'Has currency tab stack size',
cast = tonumber,
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 = '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 = util.html.abbr('Min', 'Local minimum weapon damage'),
property = 'Has minimum physical damage range average',
cast = nil,
display = h.tbl.display.factory.range{property='minimum physical damage'},
},
{
arg = 'weapon',
header = util.html.abbr('Max', 'Local maximum weapon 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 = util.html.abbr('Crit', 'Local weapon critical strike'),
property = 'Has critical strike chance range average',
cast = nil,
display = h.tbl.display.factory.range{property='critical strike chance'},
},
{
arg = 'flask_life',
header = util.html.abbr('Life', 'Life regenerated over the flask duration'),
property = 'Has flask life recovery range average',
cast = nil,
display = h.tbl.display.factory.range{property='flask life recovery'},
},
{
arg = 'flask_mana',
header = util.html.abbr('Mana', 'Mana regenerated over the flask duration'),
property = 'Has flask mana recovery range average',
cast = nil,
display = h.tbl.display.factory.range{property='flask mana recovery'},
},
{
arg = 'flask',
header = 'Duration',
property = 'Has flask duration range average',
cast = nil,
display = h.tbl.display.factory.range{property='flask duration'},
},
{
arg = 'flask',
header = util.html.abbr('Usage', 'Number of charges consumed on use'),
property = 'Has flask charges per use range average',
cast = nil,
display = h.tbl.display.factory.range{property='flask charges per use'},
},
{
arg = 'flask',
header = util.html.abbr('Capacity', 'Maximum number of flask charges held'),
property = 'Has maximum flask charges range average',
cast = nil,
display = h.tbl.display.factory.range{property='maximum flask charges'},
},
{
arg = 'stat',
header = 'Stats',
property = 'Has stat text',
cast = nil,
display = function (tr, value, data)
return h.na_or_val(tr, value, function (value)
return h.new_color('mod', value)
end)
end,
},
{
arg = 'description',
header = 'Effect(s)',
property = 'Has description',
cast = nil,
display = function (tr, value, data)
return h.na_or_val(tr, value, function (value)
return h.new_color('mod', value)
end)
end,
},
{
arg = 'flavour_text',
header = 'Flavour Text',
property = 'Has flavour text',
cast = nil,
display = function (tr, value, data)
return h.na_or_val(tr, value, function (value)
return h.new_color('flavour', value)
end)
end,
},
{
arg = 'help_text',
header = 'Help Text',
property = 'Has help text',
cast = nil,
display = function (tr, value, data)
return h.na_or_val(tr, value, function (value)
return h.new_color('help', value)
end)
end,
},
{
arg = 'buff_icon',
header = 'Buff Icon',
property = 'Has buff icon',
cast = nil,
display = function (tr, value, data)
return h.na_or_val(tr, value, function (value)
return string.format('[[%s]]', value)
end)
end,
},
}
for _, data in ipairs(core.dps_map) do
table.insert(core.result.generic_item, #core.result.generic_item-5, {
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._base_item_args = {}
g_args._mods = {}
g_args._stats = {}
g_args._subobjects = {}
g_args._properties = {}
-- Using general purpose function to handle release and removal versions
util.args.version(g_args, {frame=g_frame, set_properties=true})
-- 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
-- Base Item
core.process_base_item()
-- Prophecy special snowflake
if g_args.base_item == 'Prophecy' then
err = core.process_arguments{array=core.prophecy_args}
if err then
return err
end
g_args.inventory_icon = 'File:Prophecy inventory icon.png'
end
-- Mods
for _, k in ipairs({'implicit', 'explicit'}) do
local success = true
local i = 1
while success do
success = core.validate_mod{key=k, i=i}
i = i + 1
end
end
core.process_smw_mods()
-- Add stats - this is for when mods are not set, but we still need stats to calcuate new armour values etc
util.args.stats(g_args, {prefix='extra_'})
for _, stat in ipairs(g_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(stat.id, stat)
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
for k, data in pairs(core.stat_map) do
local value = g_args[k]
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
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
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
-- override attributes for tabula rasa
if g_args._stats.local_unique_tabula_rasa_no_requirement_or_energy_shield ~= nil and g_args._stats.local_unique_tabula_rasa_no_requirement_or_energy_shield.min == 1 then
for _, arg in ipairs({'required_strength', 'required_intelligence', 'required_dexterity', 'energy_shield'}) do
for short_key, range_data in pairs(h.range_map) do
g_args[arg .. range_data.var] = 0
end
end
g_args.required_level_final = 1
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
elseif prop == nil then
--mw.logObject(k)
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*c.image_size, height*c.image_size)
elseif width then
img = string.format('[[%s|%spx]]', img, width*c.image_size)
elseif height then
img = string.format('[[%s|x%spx]]', img, height*c.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 item-table">' .. 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
-- ----------------------------------------------------------------------------
-- Misc. Item templates
-- ----------------------------------------------------------------------------
--
-- Template:Item class
--
function p.item_class (frame)
-- Get args
g_args = getArgs(frame, {
parentFirst = true
})
g_frame = util.misc.get_frame(frame)
if not doInfoCard then
doInfoCard = require('Module:Infocard')._main
end
core.factory.table_cast('name', {key='full', tbl=m_game.constants.item.class})()
if g_args.name_list ~= nil then
g_args.name_list = util.string.split(g_args.name_list, ', ')
else
g_args.name_list = {}
end
--
local ul = mw.html.create('ul')
for _, item in ipairs(g_args.name_list) do
ul
:tag('li')
:wikitext(item)
:done()
end
-- Output Infocard
local tplargs = {
['header'] = g_args.name,
['subheader'] = '[[Item class]] ' .. 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'),
[1] = 'Also reffered to as:' .. tostring(ul),
}
-- cats
local cats = {
'Item classes',
}
-- Done
return doInfoCard(tplargs) .. util.misc.add_category(cats)
end
-- ----------------------------------------------------------------------------
-- Property views..
-- ----------------------------------------------------------------------------
function v.itembox ()
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 container = mw.html.create('span')
:attr( 'class', 'item-box -' .. g_args.frame_type)
if g_args.class == 'Divination Card' then
--TODO div card code
else
local header_css
if g_args.base_item and g_args.rarity ~= 'Normal' then
line_type = 'double'
else
line_type = 'single'
end
local name_line = g_args.name
if g_args.base_item and g_args.base_item ~= 'Prophecy' then
name_line = name_line .. '<br>' .. g_args.base_item
end
container
:tag('span')
:attr( 'class', 'header -' .. line_type )
:wikitext( name_line )
: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 = {}
if g_args.rarity == 'Unique' then
cats[#cats+1] = 'Unique ' .. g_args.class
elseif g_args.base_item == 'Prophecy' then
cats[#cats+1] = 'Prophecies'
else
cats[#cats+1] = 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
if g_args.release_version == nil then
cats[#cats+1] = 'Items without a release version'
end
--
-- Output formatting
--
return util.misc.add_category(cats, {ingore_blacklist=g_args.debug})
end
return p