Module:Item link: Difference between revisions
Jump to navigation
Jump to search
>OmegaK2 |
>OmegaK2 No edit summary |
||
| Line 1: | Line 1: | ||
-- | -- SMW reworked item module | ||
-- ---------------------------------------------------------------------------- | |||
-- TODO | |||
-- ---------------------------------------------------------------------------- | |||
-- Items | |||
-- ----- | |||
-- Check if abyss jewels actually have radius modifiers | |||
-- | |||
-- Has explicit mod ids (and others) changing order of values in the API. | |||
-- | -- | ||
-- | -- Aggregate ids from Has granted skill id from modifiers | ||
-- | -- | ||
-- | -- DROP restriction improvements: | ||
-- | -- drop monster type(s) | ||
-- | |||
-- unique items: | |||
-- 3D art | |||
-- supporter attribution | |||
-- option to hide explicit mods or override text | |||
-- | |||
-- singular for weapon class in infoboxes | |||
-- | |||
-- | |||
-- Maps: | |||
-- Area level can be retrieved eventually | |||
-- | |||
-- Essence: | |||
-- type column | |||
-- monster modifier info | |||
-- ---------- | |||
-- Item table | |||
-- ---------- | |||
-- Skills need proper range values in their tables | |||
-- stat_column<i>_stat<j>_id -> make an lua pattern alternative | |||
-- show drop area etc | |||
-- | |||
-- ---------- | |||
-- Item class | |||
-- ---------- | |||
-- | |||
-- remove the ul if name_list is not provided | |||
-- maybe smw | |||
-- ----------- | |||
-- rework todo | |||
-- ----------- | |||
-- check unique map properties copy | |||
-- REMOVED: | |||
-- is_essence | |||
-- RENAMED | |||
-- essence_tier -> essence_level | |||
-- release_version | |||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
-- Imports | -- Imports | ||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
local xtable = require('Module:Table') | |||
local m_util = require('Module:Util') | local m_util = require('Module:Util') | ||
local getArgs = require('Module:Arguments').getArgs | local getArgs = require('Module:Arguments').getArgs | ||
local m_game = require('Module:Game') | |||
local m_skill = require('Module:Skill') | |||
local m_area = require('Module:Area') | |||
local f_item_link = require('Module:Item link').item_link | |||
local cargo = mw.ext.cargo | |||
local p = {} | |||
local c = {} | |||
c.image_size = 39 | |||
c.image_size_full = c.image_size * 2 | |||
-- ---------------------------------------------------------------------------- | |||
-- Temporary fixes | |||
-- ---------------------------------------------------------------------------- | |||
-- Move to util | |||
function m_util.cast.factory.array_table(k, args) | |||
-- Arguments: | |||
-- tbl - table to check against | |||
-- errmsg - error message if no element was found; should accept 1 parameter | |||
args = args or {} | |||
return function (tpl_args, frame) | |||
local elements | |||
if tpl_args[k] ~= nil then | |||
elements = m_util.string.split(tpl_args[k], ',%s*') | |||
for _, element in ipairs(elements) do | |||
local r = m_util.table.find_in_nested_array{value=element, tbl=args.tbl, key='full'} | |||
if r == nil then | |||
error(m_util.html.error{msg=string.format(args.errmsg or i18n.errors.missing_element, element)}) | |||
end | |||
end | |||
tpl_args[args.key_out or k] = xtable:new(elements) | |||
end | |||
end | |||
end | |||
function m_util.cast.factory.assoc_table(k, args) | |||
-- Arguments: | |||
-- | |||
-- tbl | |||
-- errmsg | |||
-- key_out | |||
return function (tpl_args, frame) | |||
local elements | |||
if tpl_args[k] ~= nil then | |||
elements = m_util.string.split(tpl_args[k], ',%s*') | |||
for _, element in ipairs(elements) do | |||
if args.tbl[element] == nil then | |||
error(m_util.html.error{msg=string.format(args.errmsg or i18n.errors.missing_element, element)}) | |||
end | |||
end | |||
tpl_args[args.key_out or k] = elements | |||
end | |||
end | |||
end | |||
function m_util.cargo.query(tables, fields, query, args) | |||
-- Wrapper for mw.ext.cargo.query that helps to work around some bugs | |||
-- | |||
-- Current workarounds: | |||
-- field names will be "aliased" to themselves | |||
-- | |||
-- Takes 3 arguments: | |||
-- tables - array containing tables | |||
-- fields - array containing fields; these will automatically be renamed to the way they are specified to work around bugs when results are returned | |||
-- query - array containing cargo sql clauses | |||
-- args | |||
-- args.keep_empty | |||
-- Cargo bug workaround | |||
args = args or {} | |||
for i, field in ipairs(fields) do | |||
-- already has some alternate name set, so do not do this. | |||
if string.find(field, '=') == nil then | |||
fields[i] = string.format('%s=%s', field, field) | |||
end | |||
end | |||
local results = cargo.query(table.concat(tables, ','), table.concat(fields, ','), query) | |||
if args.keep_empty == nil then | |||
for _, row in ipairs(results) do | |||
for k, v in pairs(row) do | |||
if v == "" then | |||
row[k] = nil | |||
end | |||
end | |||
end | |||
end | |||
return results | |||
end | |||
function m_util.cargo.array_query(args) | |||
-- Performs a long "OR" query from the given array and field validating that there is only exactly one match returned | |||
-- | |||
-- args: | |||
-- tables - array of tables (see util.cargo.query) | |||
-- fields - array of fields (see util.cargo.query) | |||
-- query - array containing cargo sql clauses [optional] (see util.cargo.query) | |||
-- id_array - list of ids to query for | |||
-- id_field - name of the id field, will be automatically added to fields | |||
-- | |||
-- RETURN: | |||
-- table - results as given by mw.ext.cargo.query | |||
-- | |||
args.query = args.query or {} | |||
args.fields[#args.fields+1] = args.id_field | |||
local id_array = {} | |||
for i, id in ipairs(args.id_array) do | |||
id_array[i] = string.format('%s="%s"', args.id_field, id) | |||
end | |||
if args.query.where then | |||
args.query.where = string.format('(%s) AND (%s)', args.query.where, table.concat(id_array, ' OR ')) | |||
else | |||
args.query.where = table.concat(id_array, ' OR ') | |||
end | |||
-- | |||
-- Check for duplicates | |||
-- | |||
-- The usage of distinct should elimate duplicates here from cargo being bugged while still showing actual data duplicates. | |||
local results = m_util.cargo.query( | |||
args.tables, | |||
{ | |||
string.format('COUNT(DISTINCT %s._pageID)=count', args.tables[1]), | |||
args.id_field, | |||
}, | |||
{ | |||
join=args.query.join, | |||
where=args.query.where, | |||
groupBy=args.id_field, | |||
having=string.format('COUNT(DISTINCT %s._pageID) > 1', args.tables[1]), | |||
} | |||
) | |||
if #results > 0 then | |||
out = {} | |||
for _, row in ipairs(results) do | |||
out[#out+1] = string.format('%s (%s pages found)', row[args.id_field], row['count']) | |||
end | |||
error(string.format('Found duplicates for field "%s":\n %s', args.id_field, table.concat(out, '\n'))) | |||
end | |||
-- | |||
-- Prepare query | |||
-- | |||
if args.query.groupBy then | |||
args.query.groupBy = string.format('%s._pageID,%s', args.tables[1], args.query.groupBy) | |||
else | |||
args.query.groupBy = string.format('%s._pageID', args.tables[1]) | |||
end | |||
local results = m_util.cargo.query( | |||
args.tables, | |||
args.fields, | |||
args.query | |||
) | |||
-- | |||
-- Check missing results | |||
-- | |||
if #results ~= #args.id_array then | |||
local missing = {} | |||
for _, id in ipairs(args.id_array) do | |||
missing[id] = true | |||
end | |||
for _, row in ipairs(results) do | |||
missing[row[args.id_field]] = nil | |||
end | |||
local missing_ids = {} | |||
for k, _ in pairs(missing) do | |||
missing_ids[#missing_ids+1] = k | |||
end | |||
error(string.format('Missing results for "%s" field with values: \n%s', args.id_field, table.concat(missing_ids, '\n'))) | |||
end | |||
return results | |||
end | |||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
| Line 22: | Line 253: | ||
-- TODO: Maybe move this out to a separate sub-page module | -- TODO: Maybe move this out to a separate sub-page module | ||
local i18n = { | local i18n = { | ||
range = '(%s to %s)', | |||
inventory_icon = 'File:%s inventory icon.png', | |||
status_icon = 'File:%s status icon.png', | |||
skill_screenshot = 'File:%s skill screenshot.jpg', | |||
divination_card_art = 'File:%s card art.png', | |||
gem_tag_category = '[[:Category:%s (gem tag)|%s]]', | |||
categories = { | categories = { | ||
-- maintenance cats | -- maintenance cats | ||
improper_modifiers = 'Items with improper modifiers', | |||
missing_release_version = 'Items without a release version', | |||
broken_upgraded_from_reference = 'Items with broken item references in upgraded from parameters', | |||
-- regular cats | |||
alternate_artwork = 'Items with alternate artwork', | |||
-- misc | |||
gem_tag_affix = '%s (gem tag)', | |||
unique_affix = 'Unique %s', | |||
prophecies = 'Prophecies', | |||
talismans = 'Talismans', | |||
essences = 'Essences', | |||
}, | |||
stat_skip_patterns = { | |||
maps = { | |||
'%d+%% increased Quantity of Items found in this Area', | |||
'%d+%% increased Rarity of Items found in this Area', | |||
'%+%d+%% Monster pack size', | |||
-- ranges | |||
'%(%d+%-%d+%)%% increased Quantity of Items found in this Area', | |||
'%(%d+%-%d+%)%% increased Rarity of Items found in this Area', | |||
'%+%(%d+%-%d+%)%% Monster pack size', | |||
}, | |||
jewels = { | |||
'Limited to %d+ %(Hidden%)', | |||
'Jewel has a radius of %d+ %(Hidden%)', | |||
}, | |||
}, | }, | ||
help_text_defaults = { | |||
active_gem = 'Place into an item socket of the right colour to gain this skill. Right click to remove from a socket.', | |||
support_gem = 'This is a Support Gem. It does not grant a bonus to your character, but skills in sockets connected to it. Place into an item socket connected to a socket containing the Active Skill Gem you wish to augment. Right click to remove from a socket.', | |||
hideout_doodad = 'Right click on this item then left click on a location on the ground to create the object.', | |||
jewel = 'Place into an allocated Jewel Socket on the Passive Skill Tree. Right click to remove from the Socket.', | |||
}, | |||
-- Used by the item table | |||
item_table = { | |||
item = 'Item', | |||
skill_gem = 'Skill gem', | |||
physical_dps = m_util.html.abbr('pDPS', 'physical damage per second'), | |||
fire_dps = m_util.html.abbr('Fire DPS', 'fire damage per second'), | |||
cold_dps = m_util.html.abbr('Cold DPS', 'cold damage per second'), | |||
lightning_dps = m_util.html.abbr('Light. DPS', 'lightning damage per second'), | |||
chaos_dps = m_util.html.abbr('Chaos DPS', 'chaos damage per second'), | |||
elemental_dps = m_util.html.abbr('eDPS', 'elemental damage (i.e. fire/cold/lightning) per second'), | |||
poison_dps = m_util.html.abbr('Poison DPS', 'poison damage (i.e. physical/chaos) per second'), | |||
dps = m_util.html.abbr('DPS', 'total damage (i.e. physical/fire/cold/lightning/chaos) per second'), | |||
base_item = 'Base Item', | |||
item_class = 'Item Class', | |||
essence_level = 'Essence<br>Level', | |||
drop_level = 'Drop<br>Level', | |||
drop_leagues = 'Drop Leagues', | |||
drop_areas = 'Drop Areas', | |||
drop_text = 'Additional<br>Drop Restrictions', | |||
stack_size = 'Stack<br>Size', | |||
stack_size_currency_tab = m_util.html.abbr('Tab<br>Stack<br>Size', 'Stack size in the currency stash tab'), | |||
armour = m_util.html.abbr('AR', 'Armour'), | |||
evasion = m_util.html.abbr('EV', 'Evasion Rating'), | |||
energy_shield = m_util.html.abbr('ES', 'Energy Shield'), | |||
block = m_util.html.abbr('Block', 'Chance to Block'), | |||
damage = m_util.html.abbr('Damage', 'Colour coded damage'), | |||
attacks_per_second = m_util.html.abbr('APS', 'Attacks per second'), | |||
local_critical_strike_chance = m_util.html.abbr('Crit', 'Local weapon critical strike chance'), | |||
flask_life = m_util.html.abbr('Life', 'Life regenerated over the flask duration'), | |||
flask_mana = m_util.html.abbr('Mana', 'Mana regenerated over the flask duration'), | |||
flask_duration = 'Duration', | |||
flask_charges_per_use = m_util.html.abbr('Usage', 'Number of charges consumed on use'), | |||
flask_maximum_charges = m_util.html.abbr('Capacity', 'Maximum number of flask charges held'), | |||
item_limit = 'Limit', | |||
jewel_radius = 'Radius', | |||
map_tier = 'Map<br>Tier', | |||
map_level = 'Map<br>Level', | |||
map_guild_character = m_util.html.abbr('Char', 'Character for the guild tag'), | |||
buff_effects = 'Buff Effects', | |||
stats = 'Stats', | |||
effects = 'Effect(s)', | |||
flavour_text = 'Flavour Text', | |||
prediction_text = 'Prediction', | |||
help_text = 'Help Text', | |||
seal_cost = m_util.html.abbr('Seal<br>Cost', 'Silver Coin cost of sealing this prophecies into an item'), | |||
objective = 'Objective', | |||
reward = 'Reward', | |||
buff_icon = 'Buff<br>Icon', | |||
-- Skills | |||
support_gem_letter = m_util.html.abbr('L', 'Support gem letter.'), | |||
skill_icon = 'Icon', | |||
description = 'Description', | |||
skill_critical_strike_chance = m_util.html.abbr('Crit', 'Critical Strike Chance'), | |||
cast_time = m_util.html.abbr('Cast<br>Time', 'Casting time of the skill in seconds'), | |||
damage_effectiveness = m_util.html.abbr('Dmg.<br>Eff.', 'Damage Effectiveness'), | |||
mana_cost_multiplier = m_util.html.abbr('MCM', 'Mana cost multiplier - missing values indicate it changes on gem level'), | |||
mana_cost = m_util.html.abbr('Mana', 'Mana cost'), | |||
reserves_mana_suffix = m_util.html.abbr('R', 'reserves mana'), | |||
vaal_souls_requirement = m_util.html.abbr('Souls', 'Vaal souls requirement in Normal/Cruel/Merciless difficulty'), | |||
stored_uses = m_util.html.abbr('Uses', 'Maximum number of stored uses'), | |||
primary_radius = m_util.html.abbr('R1', 'Primary radius'), | |||
secondary_radius = m_util.html.abbr('R2', 'Secondary radius'), | |||
tertiary_radius = m_util.html.abbr('R3', 'Tertiary radius'), | |||
}, | |||
-- Used by the item info box | |||
tooltips = { | |||
corrupted = 'Corrupted', | |||
support_icon = 'Icon: %s', | |||
radius = 'Radius: %s', | |||
mana_reserved = 'Mana Reserved: %s', | |||
mana_cost = 'Mana Cost: %s', | |||
mana_multiplier = 'Mana Multiplier: %s', | |||
vaal_souls_per_use = 'Souls per use: %s', | |||
stored_uses = 'Can store %s use(s)', | |||
cooldown_time = 'Cooldown Time: %s', | |||
cast_time = 'Cast Time: %s', | |||
critical_strike_chance = 'Critical Strike Chance: %s', | |||
damage_effectiveness = 'Damage Effectiveness: %s', | |||
projectile_speed = 'Projectile Speed: %s', | |||
quality = 'Quality: %s', | |||
physical_damage = 'Physical Damage: %s', | |||
elemental_damage = 'Elemental Damage:%s', | |||
chaos_damage = 'Chaos Damage: %s', | |||
attacks_per_second = 'Attacks per Second: %s', | |||
weapon_range = 'Weapon Range: %s', | |||
map_level = 'Map Level: %s', | |||
map_tier = 'Map Tier: %s', | |||
map_guild_character = m_util.html.abbr('Guild Character', 'When used in guild creation, this map can be used for the listed character') .. ': %s', | |||
item_quantity = 'Item Quantity: %s', | |||
item_rarity = 'Item Rarity: %s', | |||
monster_pack_size = 'Monster Pack Size: %s', | |||
limited_to = 'Limited to: %s', | |||
flask_mana_recovery = 'Recovers %s Mana over %s seconds', | |||
flask_life_recovery = 'Recovers %s Life over %s seconds', | |||
flask_duration = 'Lasts %s Seconds', | |||
flask_charges_per_use = 'Consumes %s of %s Charges on use', | |||
chance_to_block = 'Chance to Block: %s', | |||
armour = 'Armour: %s', | |||
evasion = 'Evasion: %s', | |||
energy_shield = 'Energy Shield: %s', | |||
talisman_tier = 'Talisman Tier: %s', | |||
stack_size = 'Stack Size: %s', | |||
essence_level = 'Essence Level: %s', | |||
requires = 'Requires %s', | |||
level_inline = 'Level %s', | |||
level = 'Level: %s', | |||
gem_quality = 'Per 1% Quality:', | |||
variation_singular = 'Variation', | |||
variation_plural = 'Variations', | |||
favour_cost = 'Favour cost: %s', | |||
seal_cost = 'Seal cost: <br>%s', | |||
cannot_be_traded_or_modified = 'Cannot be traded or modified', | |||
-- secondary infobox | |||
drop_restrictions = 'Acquisition', | |||
league_restriction = m_util.html.abbr('League(s):', 'Item can be obtained in relation to these league(s)') .. ' %s', | |||
drop_disabled = 'DROP DISABLED', | |||
purchase_costs = m_util.html.abbr('Purchase Costs', 'Cost of purchasing an item of this type at NPC vendors. This does not indicate whether NPCs actually sell the item.'), | |||
sell_price = m_util.html.abbr('Sell Price', 'Items or currency received when selling this item at NPC vendors. Certain vendor recipes may override this value.'), | |||
damage_per_second = 'Weapon DPS', | |||
physical_dps = 'Physical', | |||
fire_dps = 'Fire', | |||
cold_dps = 'Cold', | |||
lightning_dps = 'Lightning', | |||
chaos_dps = 'Chaos', | |||
elemental_dps = 'Elemental', | |||
poison_dps = 'Phys+Chaos', | |||
dps = 'Total', | |||
}, | |||
acquisition = { | |||
header = 'Item acquisition', | |||
area = 'This item can be acquired in the following areas:', | |||
upgraded_from = 'This item can be acquired through the following upgrade paths or vendor recipes:', | |||
ingredient_header = 'Usage in recipes', | |||
ingredient = 'This item is used by upgrade paths or vendor recipes to create the following items:', | |||
}, | |||
item_class_infobox = { | |||
page = '[[Item class]]', | |||
info = m_util.html.abbr('(?)', 'Item classes categorize items. Classes are often used to restrict items or skill gems to a specific class or by item filters'), | |||
also_referred_to_as = 'Also referred to as:', | |||
}, | |||
debug = { | |||
base_item_field_not_found = 'Base item property not found: %s.%s', | |||
field_value_mismatch = 'Value for argument "%s" is set to something else then default: %s', | |||
}, | |||
errors = { | errors = { | ||
missing_base_item = 'Rarity is set to above normal, but base item is not set. A base item for rarities above normal is required!', | |||
missing_rarity = 'Base item parameter is set, but rarity is set to normal. A rarity above normal is required!', | |||
missing_amount = 'Item amount is missing or not a number (%s)', | |||
upgraded_from_broken_reference = 'Item reference in %s is broken (results: %s)', | |||
duplicate_base_items = 'More then one result found for the specified base item. Consider using base_item_page or base_item_id to narrow down the results.', | |||
invalid_league = '%s is not a recognized league', | |||
invalid_tag = '%s is not a valid tag', | |||
generic_argument_parameter = 'Unrecognized %s parameter "%s"', | |||
invalid_item_class = 'Invalid item class', | |||
invalid_item_table_mode = 'Invalid mode for item table', | |||
non_unique_relic = 'Only unique items can be be relics', | |||
}, | }, | ||
} | } | ||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
-- | -- Other stuff | ||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
local | local h = {} | ||
function h.debug(tpl_args, func) | |||
if tpl_args.debug == nil then | |||
return | |||
end | |||
func() | |||
end | |||
function h.na_or_val(tr, value, func) | |||
if value == nil then | |||
tr:wikitext(m_util.html.td.na()) | |||
else | |||
local raw_value = value | |||
if func ~= nil then | |||
value = func(value) | |||
end | |||
tr | |||
:tag('td') | |||
:attr('data-sort-value', raw_value) | |||
:wikitext(value) | |||
:done() | |||
end | |||
end | |||
-- helper to loop over the range variables easier | |||
h.range_map = { | |||
min = { | |||
var = '_range_minimum', | |||
}, | |||
max = { | |||
var = '_range_maximum', | |||
}, | |||
avg = { | |||
var = '_range_average', | |||
}, | |||
} | |||
h.range_fields = { | |||
{ | |||
field = '_range_minimum', | |||
type = 'Integer', | |||
}, | |||
{ | |||
field = '_range_maximum', | |||
type = 'Integer', | |||
}, | |||
{ | |||
field = '_range_average', | |||
type = 'Integer', | |||
}, | |||
{ | |||
field = '_range_text', | |||
type = 'Text', | |||
}, | |||
{ | |||
field = '_range_colour', | |||
type = 'String', | |||
}, | |||
{ | |||
field = '_html', | |||
type = 'Text', | |||
}, | |||
} | } | ||
function h.handle_range_args(tpl_args, frame, argument_key, field, value, fmt_options) | |||
fmt_options = mw.clone(fmt_options) | |||
fmt_options.return_color = true | |||
local html, colour = h.format_value(tpl_args, frame, value, fmt_options) | |||
tpl_args[argument_key .. '_html'] = html | |||
tpl_args[field .. '_html'] = html | |||
tpl_args[field .. '_range_colour'] = colour | |||
fmt_options = mw.clone(fmt_options) | |||
fmt_options.no_color = true | |||
tpl_args[field .. '_range_text'] = h.format_value(tpl_args, frame, value, fmt_options) | |||
end | |||
function h.stats_update(tpl_args, id, value, modid, key) | |||
if tpl_args[key][id] == nil then | |||
tpl_args[key][id] = { | |||
references = {modid}, | |||
min = value.min, | |||
max = value.max, | |||
avg = value.avg, | |||
} | |||
else | |||
if modid ~= nil then | |||
table.insert(tpl_args[key][id].references, modid) | |||
end | |||
tpl_args[key][id].min = tpl_args[key][id].min + value.min | |||
tpl_args[key][id].max = tpl_args[key][id].max + value.max | |||
tpl_args[key][id].avg = tpl_args[key][id].avg + value.avg | |||
end | |||
end | |||
h.stat = {} | |||
function h.stat.add (value, stat_cached) | |||
value.min = value.min + stat_cached.min | |||
value.max = value.max + stat_cached.max | |||
end | |||
function h.stat.more (value, stat_cached) | |||
value.min = value.min * (1 + stat_cached.min / 100) | |||
value.max = value.max * (1 + stat_cached.max / 100) | |||
end | |||
function h.stat.more_inverse (value, stat_cached) | |||
value.min = value.min / (1 + stat_cached.min / 100) | |||
value.max = value.max / (1 + stat_cached.max / 100) | |||
end | |||
h.tbl = {} | |||
function h.tbl.range_fields(field) | |||
return function() | |||
local fields = {} | |||
for _, partial_field in ipairs({'maximum', 'text', 'colour'}) do | |||
fields[#fields+1] = string.format('%s_range_%s', field, partial_field) | |||
end | |||
return fields | |||
end | |||
end | |||
h.tbl.display = {} | |||
function h.tbl.display.na_or_val(tr, value, data) | |||
return h.na_or_val(tr, value) | |||
end | |||
function h.tbl.display.seconds(tr, value, data) | |||
return h.na_or_val(tr, value, function(value) | |||
return string.format('%ss', value) | |||
end) | |||
end | |||
function h.tbl.display.percent(tr, value, data) | |||
return h.na_or_val(tr, value, function(value) | |||
return string.format('%s%%', value) | |||
end) | |||
end | |||
function h.tbl.display.wikilink(tr, value, data) | |||
return h.na_or_val(tr, value, function(value) | |||
return string.format('[[%s]]', value) | |||
end) | |||
end | |||
h.tbl.display.factory = {} | |||
function h.tbl.display.factory.value(args) | |||
args.options = args.options or {} | |||
return function(tr, data, properties) | |||
values = {} | |||
for index, prop in ipairs(properties) do | |||
local value = data[prop] | |||
if args.options[index] and args.options[index].fmt then | |||
value = string.format(args.options[index].fmt, value) | |||
end | |||
values[#values+1] = value | |||
end | |||
local td = tr:tag('td') | |||
td:attr('data-sort-value', table.concat(values, ', ')) | |||
td:wikitext(table.concat(values, ', ')) | |||
if args.colour then | |||
td:attr('class', 'tc -' .. args.colour) | |||
end | |||
end | |||
end | |||
function h.tbl.display.factory.range(args) | |||
-- args: table | |||
-- property | |||
return function (tr, data, fields) | |||
tr | |||
:tag('td') | |||
:attr('data-sort-value', data[string.format('%s_range_maximum', args.field)] or '0') | |||
:attr('class', 'tc -' .. (data[string.format('%s_range_colour', args.field)] or 'default')) | |||
:wikitext(data[string.format('%s_range_text', args.field)]) | |||
:done() | |||
end | |||
end | |||
function h.format_value(tpl_args, frame, value, options) | |||
-- value: table | |||
-- min: | |||
-- max: | |||
-- options: table | |||
-- fmt: formatter to use for the value instead of valfmt | |||
-- fmt_range: formatter to use for the range values. Default: (%s to %s) | |||
-- inline: Use this format string to insert value | |||
-- inline_color: colour to use for the inline value; false to disable colour | |||
-- func: Function to adjust the value with before output | |||
-- color: colour code for m_util.html.poe_color, overrides mod colour | |||
-- no_color: set to true to ingore colour entirely | |||
-- return_color: also return colour | |||
if options.no_color == nil then | |||
if options.color then | |||
value.color = options.color | |||
elseif value.base ~= value.min or value.base ~= value.max then | |||
value.color = 'mod' | |||
else | |||
value.color = 'value' | |||
end | |||
end | |||
if options.func ~= nil then | |||
value.min = options.func(tpl_args, frame, value.min) | |||
value.max = options.func(tpl_args, frame, value.max) | |||
end | |||
if options.fmt == nil then | |||
options.fmt = '%s' | |||
elseif type(options.fmt) == 'function' then | |||
options.fmt = options.fmt(tpl_args, frame) | |||
end | |||
if value.min == value.max then | |||
value.out = string.format(options.fmt, value.min) | |||
else | |||
value.out = string.format(string.format(options.fmt_range or i18n.range, options.fmt, options.fmt), value.min, value.max) | |||
end | |||
if options.no_color == nil then | |||
value.out = m_util.html.poe_color(value.color, value.out) | |||
end | |||
local return_color | |||
if options.return_color ~= nil then | |||
return_color = value.color | |||
end | |||
local text = options.inline | |||
if type(text) == 'string' then | |||
elseif type(text) == 'function' then | |||
text = text(tpl_args, frame) | |||
else | |||
text = nil | |||
end | |||
if text and text ~= '' then | |||
local color | |||
if options.inline_color == nil then | |||
color = 'default' | |||
elseif options.inline_color ~= false then | |||
color = color.inline_color | |||
end | |||
if color ~= nil then | |||
text = m_util.html.poe_color(color, text) | |||
end | |||
return string.format(text, value.out), return_color | |||
end | |||
-- If we didn't return before, return here | |||
return value.out, return_color | |||
end | |||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
-- | -- core | ||
-- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||
local core = {} | |||
function core.build_cargo_data(tpl_args, frame) | |||
for _, table_name in ipairs(core.item_classes[tpl_args.class].tables) do | |||
for k, field_data in pairs(core.cargo[table_name].fields) do | |||
field_data.table = table_name | |||
for _, stat_data in pairs(core.stat_map) do | |||
if stat_data == k then | |||
for _, range_field in ipairs(h.range_fields) do | |||
local field_name = stat_data.field .. range_field.field | |||
local data = { | |||
no_copy = true, | |||
table = table_name, | |||
field = field_name, | |||
type = range_field.type, | |||
} | |||
core.cargo[table_name].fields[field_name] = data | |||
core.map[field_name] = data | |||
end | |||
break | |||
end | |||
end | |||
if table_name == 'weapons' then | |||
for _, dps_data in ipairs(core.dps_map) do | |||
for _, range_field in ipairs(h.range_fields) do | |||
local field_name = dps_data.field .. range_field.field | |||
local data = { | |||
no_copy = true, | |||
table = table_name, | |||
field = field_name, | |||
type = range_field.type, | |||
} | |||
core.cargo[table_name].fields[field_name] = data | |||
core.map[field_name] = data | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
function core.validate_mod(tpl_args, frame, args) | |||
-- args: | |||
-- key - implict or explicit | |||
-- i | |||
-- value | |||
local value = tpl_args[args.key .. args.i] | |||
local out = { | |||
result=nil, | |||
modid=nil, | |||
type=args.key, | |||
text=nil, | |||
} | |||
if value ~= nil then | |||
table.insert(tpl_args.mods, value) | |||
table.insert(tpl_args[args.key .. '_mods'], value) | |||
out.modid = value | |||
out.text = tpl_args[args.key .. args.i .. '_text'] | |||
--out.result = nil | |||
table.insert(tpl_args._mods, out) | |||
return true | |||
else | |||
value = tpl_args[args.key .. args.i .. '_text'] | |||
if value ~= nil then | |||
tpl_args._flags.text_modifier = true | |||
out.result = value | |||
table.insert(tpl_args._mods, out) | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
function core.process_smw_mods(tpl_args, frame) | |||
tpl_args.sell_prices = {} | |||
if #tpl_args.mods > 0 then | |||
local mods = {} | |||
local mod_ids = {} | |||
for _, mod_data in ipairs(tpl_args._mods) do | |||
if mod_data.result == nil then | |||
mods[mod_data.modid] = mod_data | |||
mod_ids[#mod_ids+1] = mod_data.modid | |||
mods[#mods+1] = string.format('mods.id="%s"', mod_data.modid) | |||
end | |||
end | |||
local results = m_util.cargo.array_query{ | |||
tables={'mods'}, | |||
fields={'mods._pageName', 'mods.id', 'mods.required_level', 'mods.stat_text'}, | |||
id_field='mods.id', | |||
id_array=mod_ids, | |||
} | |||
for _, data in ipairs(results) do | |||
local mod_data = mods[data['mods.id']] | |||
mod_data.result = data | |||
-- update item level requirement | |||
local keys = {'required_level_final'} | |||
-- only update base item requirement if this is an implicit | |||
if mod_data.key == 'implicit' then | |||
keys[#keys+1] = 'required_level' | |||
end | |||
for _, key in ipairs(keys) do | |||
local req = math.floor(tonumber(data['mods.required_level']) * 0.8) | |||
if req > tpl_args[key] then | |||
tpl_args[key] = req | |||
end | |||
end | |||
end | |||
-- fetch stats | |||
results = cargo.query( | |||
'mods,mod_stats', | |||
-- Workaround: Cargo messing up the ids here | |||
'mods.id=mods.id, mod_stats.id=mod_stats.id, mod_stats.min, mod_stats.max', | |||
{ | |||
join='mods._pageID=mod_stats._pageID', | |||
where='mod_stats.id IS NOT NULL AND (' .. table.concat(mods, ' OR ') .. ')', | |||
-- Workaround: Fix cargo duplicates | |||
groupBy='mod_stats._pageID, mod_stats.id', | |||
} | |||
) | |||
for _, data in ipairs(results) do | |||
-- Stat subobject | |||
local mod_data = mods[data['mods.id']] | |||
if mod_data.result.stats == nil then | |||
mod_data.result.stats = {data, } | |||
else | |||
mod_data.result.stats[#mod_data.result.stats+1] = data | |||
end | |||
local id = data['mod_stats.id'] | |||
local value = { | |||
min = tonumber(data['mod_stats.min']), | |||
max = tonumber(data['mod_stats.max']), | |||
} | |||
value.avg = (value.min+value.max)/2 | |||
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], '_stats') | |||
if mod_data.type ~= 'implicit' then | |||
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], '_explicit_stats') | |||
else | |||
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], '_implicit_stats') | |||
end | |||
end | |||
-- fetch sell prices | |||
results = cargo.query( | |||
'mods,mod_sell_prices', | |||
'mods.id, mod_sell_prices.amount, mod_sell_prices.name', | |||
{ | |||
join='mods._pageID=mod_sell_prices._pageID', | |||
where='mod_sell_prices.amount IS NOT NULL AND (' .. table.concat(mods, ' OR ') .. ')', | |||
-- Workaround: Fix cargo duplicates | |||
groupBy='mod_sell_prices._pageID, mod_sell_prices.name', | |||
} | |||
) | |||
for _, data in ipairs(results) do | |||
local mod_data = mods[data['mods.id']] | |||
if mod_data.type ~= implicit then | |||
local values = { | |||
name = data['mod_sell_prices.name'], | |||
amount = tonumber(data['mod_sell_prices.amount']), | |||
} | |||
tpl_args.sell_prices[values.name] = (tpl_args.sell_prices[values.name] or 0) + values.amount | |||
end | |||
end | |||
end | |||
local missing_sell_price = true | |||
for _, _ in pairs(tpl_args.sell_prices) do | |||
missing_sell_price = false | |||
break | |||
end | |||
if missing_sell_price then | |||
tpl_args.sell_prices['Scroll Fragment'] = 1 | |||
end | |||
-- Set sell price on page | |||
tpl_args.sell_price_order = {} | |||
for name, amount in pairs(tpl_args.sell_prices) do | |||
tpl_args.sell_price_order[#tpl_args.sell_price_order+1] = name | |||
tpl_args._subobjects[#tpl_args._subobjects+1] = { | |||
_table = 'item_sell_prices', | |||
amount = amount, | |||
item_name = name, | |||
} | |||
end | |||
table.sort(tpl_args.sell_price_order) | |||
end | |||
function core.process_base_item(tpl_args, frame, args) | |||
local where | |||
if tpl_args.base_item_id ~= nil then | |||
where = string.format('items.metadata_id="%s"', tpl_args.base_item_id) | |||
elseif tpl_args.base_item_page ~= nil then | |||
where = string.format('items._pageName="%s"' , tpl_args.base_item_page) | |||
elseif tpl_args.base_item ~= nil then | |||
where = string.format('items.name="%s"' , tpl_args.base_item) | |||
elseif tpl_args.rarity ~= 'Normal' then | |||
error(m_util.html.error{msg=i18n.errors.missing_base_item}) | |||
else | |||
return | |||
end | |||
if where ~= nil and tpl_args.rarity == 'Normal' then | |||
error(m_util.html.error{msg=i18n.errors.missing_rarity}) | |||
end | |||
where = string.format('%s AND items.class="%s" AND items.rarity="Normal"', where, tpl_args.class) | |||
local join = {} | |||
for _, table_name in ipairs(core.item_classes[tpl_args.class].tables) do | |||
if table_name ~= 'items' then | |||
join[#join+1] = string.format('items._pageID=%s._pageID', table_name) | |||
end | |||
end | |||
local fields = { | |||
'items._pageName=items._pageName', | |||
'items.name=items.name', | |||
'items.metadata_id=items.metadata_id', | |||
} | |||
for _, k in ipairs(tpl_args._base_item_args) do | |||
local data = core.map[k] | |||
if data.field ~= nil then | |||
fields[#fields+1] = string.format('%s.%s=%s.%s', data.table, data.field, data.table, data.field) | |||
end | |||
end | |||
local result = cargo.query( | |||
table.concat(core.item_classes[tpl_args.class].tables, ','), | |||
table.concat(fields, ','), | |||
{ | |||
where=where, | |||
join=table.concat(join, ','), | |||
-- Workaround: Fix cargo duplicates | |||
groupBy='items._pageID', | |||
} | |||
) | |||
if #result > 1 then | |||
error(m_util.html.error{msg=i18n.errors.duplicate_base_items}) | |||
-- TODO be more explicit in the error? | |||
end | |||
result = result[1] | |||
--error(mw.dumpObject(result)) | |||
tpl_args.base_item_data = result | |||
core.process_arguments(tpl_args, frame, {array={'base_item', 'base_item_page', 'base_item_id'}}) | |||
--Copy values.. | |||
for _, k in ipairs(tpl_args._base_item_args) do | |||
local data = core.map[k] | |||
if data.field ~= nil and data.func_fetch == nil then | |||
local value = result[string.format('%s.%s', data.table, data.field)] | |||
-- I can just use data.default since it will be nil if not provided (nil == nil). Neat! ;) | |||
if value ~= "" and (tpl_args[k] == data.default or type(data.default) == 'function') then | |||
tpl_args[k] = value | |||
if data.func ~= nil then | |||
data.func(tpl_args, frame) | |||
end | |||
if data.func_copy ~= nil then | |||
data.func_copy(tpl_args, frame) | |||
end | |||
elseif value == "" then | |||
h.debug(tpl_args, function () | |||
mw.logObject(string.format(i18n.debug.base_item_field_not_found, data.table, data.field)) | |||
end) | |||
elseif tpl_args[k] ~= data.default then | |||
h.debug(tpl_args, function () | |||
mw.logObject(string.format(i18n.debug.field_value_mismatch, k, tostring(tpl_args[k]))) | |||
end) | |||
end | |||
elseif data.func_fetch ~= nil then | |||
data.func_fetch(tpl_args, frame) | |||
end | |||
end | |||
end | |||
function core.process_arguments(tpl_args, frame, args) | |||
for _, k in ipairs(args.array) do | |||
local data = core.map[k] | |||
if data == nil then | |||
error(string.format('Invalid key or missing data for "%s"', k)) | |||
end | |||
if data.no_copy == nil then | |||
table.insert(tpl_args._base_item_args, k) | |||
end | |||
if data.func ~= nil then | |||
data.func(tpl_args, frame) | |||
--[[local status, err = pcall(data.func) | |||
-- an error was raised, return the error string instead of the template | |||
if not status then | |||
return err | |||
end | |||
]]-- | |||
end | |||
if tpl_args[k] == nil then | |||
if tpl_args.class and core.item_classes[tpl_args.class].defaults ~= nil and core.item_classes[tpl_args.class].defaults[k] ~= nil then | |||
tpl_args[k] = core.item_classes[tpl_args.class].defaults[k] | |||
elseif data.default ~= nil then | |||
if type(data.default) == 'function' then | |||
tpl_args[k] = data.default() | |||
else | |||
tpl_args[k] = data.default | |||
end | |||
end | |||
end | |||
end | |||
end | |||
function core.process_mod_stats(tpl_args, args) | |||
local lines = {} | |||
local skip = core.class_specifics[tpl_args.class] | |||
if skip then | |||
skip = skip.skip_stat_lines | |||
end | |||
for _, modinfo in ipairs(tpl_args._mods) do | |||
if modinfo.type == args.type then | |||
if modinfo.modid == nil then | |||
table.insert(lines, modinfo.result) | |||
-- Allows the override of the SMW fetched mod texts for this modifier via <modtype><id>_text parameter | |||
elseif modinfo.text ~= nil then | |||
if m_util.cast.boolean(modinfo.text) then | |||
table.insert(lines, modinfo.text) | |||
end | |||
else | |||
for _, line in ipairs(m_util.string.split(modinfo.result['mods.stat_text'], '<br>')) do | |||
if line ~= '' then | |||
if skip == nil then | |||
table.insert(lines, line) | |||
else | |||
local skipped = false | |||
for _, pattern in ipairs(skip) do | |||
if string.match(line, pattern) then | |||
skipped = true | |||
break | |||
end | |||
end | |||
if not skipped then | |||
table.insert(lines, line) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
if #lines == 0 then | |||
return | |||
else | |||
return table.concat(lines, '<br>') | |||
end | |||
end | |||
function core.process_upgraded_from(tpl_args, frame) | |||
local sets = {} | |||
local setid = 1 | |||
local set | |||
repeat | |||
local prefix = string.format('upgraded_from_set%s_', setid) | |||
local groupid = 1 | |||
local group | |||
set = { | |||
groups = {}, | |||
optional = m_util.cast.boolean(tpl_args[prefix .. 'optional']), | |||
text = tpl_args[prefix .. 'text'], | |||
} | |||
repeat | |||
local group_prefix = string.format('%sgroup%s_', prefix, groupid) | |||
group = { | |||
item_name = tpl_args[group_prefix .. 'item_name'], | |||
item_id = tpl_args[group_prefix .. 'item_id'], | |||
item_page = tpl_args[group_prefix .. 'item_page'], | |||
amount = tonumber(tpl_args[group_prefix .. 'amount']), | |||
notes = tpl_args[group_prefix .. 'notes'], | |||
} | |||
if group.item_name ~= nil or group.item_id ~= nil or group.item_page ~= nil then | |||
if group.amount == nil then | |||
error(string.format(i18n.errors.missing_amount, group_prefix .. 'amount')) | |||
else | |||
-- for verification purposes | |||
local where | |||
if group.item_id then | |||
where = string.format('items.metadata_id="%s"', group.item_id) | |||
elseif group.item_page then | |||
where = string.format('items._pageName="%s"', group.item_page) | |||
elseif group.item_name then | |||
where = string.format('items.name="%s"', group.item_name) | |||
end | |||
local results = cargo.query( | |||
'items', | |||
'items._pageName, items.name, items.metadata_id', | |||
{ | |||
where=where, | |||
-- Workaround: Fix cargo duplicates | |||
groupBy='items._pageID', | |||
} | |||
) | |||
if #results ~= 1 then | |||
tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.upgraded_from_broken_reference, string.sub(group_prefix, 1, -2), #results) | |||
tpl_args._flags.broken_upgraded_from_reference = true | |||
else | |||
results = results[1] | |||
if results['items.metadata_id'] ~= '' then | |||
group.item_id = results['items.metadata_id'] | |||
end | |||
group.item_name = results['items.name'] | |||
group.page = results['items._pageName'] | |||
set.groups[#set.groups+1] = group | |||
end | |||
end | |||
end | |||
groupid = groupid + 1 | |||
until group.item_name == nil and group.item_id == nil and group.item_page == nil | |||
-- set was empty, can terminate safely | |||
if #set.groups == 0 then | |||
set = nil | |||
else | |||
setid = setid + 1 | |||
sets[#sets+1] = set | |||
end | |||
until set == nil | |||
if #sets == 0 then | |||
return | |||
end | |||
tpl_args.upgrade_from_sets = sets | |||
-- set upgraded_from data | |||
for i, set in ipairs(sets) do | |||
tpl_args._subobjects[#tpl_args._subobjects+1] = { | |||
_table = 'upgraded_from_sets', | |||
set_id = i, | |||
text = set.text, | |||
--'optional' = set.optional, | |||
} | |||
for j, group in ipairs(set.groups) do | |||
tpl_args._subobjects[#tpl_args._subobjects+1] = { | |||
_table = 'upgraded_from_groups', | |||
group_id = j, | |||
set_id = i, | |||
item_name = group.item_name, | |||
item_id = group.item_id, | |||
item_page = group.page, | |||
amount = group.amount, | |||
notes = group.notes, | |||
} | |||
end | |||
end | |||
end | |||
-- | |||
-- function factory | |||
-- | |||
core.factory = {} | |||
function core.factory.display_value(args) | |||
-- args: | |||
-- type: Type of the keys (nil = regular, gem = skill gems, stat = stats) | |||
-- options<Array>: | |||
-- key: key to use | |||
-- allow_zero: allow zero values | |||
-- hide_default: hide the value if this is set | |||
-- hide_default_key: key to use if it isn't equal to the key parameter | |||
-- -- from h.format_value -- | |||
-- fmt: formatter to use for the value instead of valfmt | |||
-- fmt_range: formatter to use for the range values. Default: (%s to %s) | |||
-- insert: insert results into this object | |||
-- func: Function to adjust the value with before output | |||
-- color: colour code for m_util.html.poe_color, overrides mod colour | |||
-- no_color: set to true to ingore colour entirely | |||
for k, default in pairs({options = {}}) do | |||
if args[k] == nil then | |||
args[k] = default | |||
end | |||
end | |||
return function (tpl_args, frame) | |||
local base_values = {} | |||
local temp_values = {} | |||
if args.type == 'gem' then | |||
if not core.class_groups.gems.keys[tpl_args.class] then | |||
return | |||
end | |||
for i, data in ipairs(args.options) do | |||
local value = tpl_args.skill_levels[0][data.key] | |||
if value ~= nil then | |||
base_values[#base_values+1] = value | |||
temp_values[#temp_values+1] = {value={min=value,max=value}, index=i} | |||
else | |||
value = { | |||
min=tpl_args.skill_levels[1][data.key], | |||
max=tpl_args.skill_levels[tpl_args.max_level][data.key], | |||
} | |||
if value.min == nil or value.max == nil then | |||
else | |||
base_values[#base_values+1] = value.min | |||
temp_values[#temp_values+1] = {value=value, index=i} | |||
end | |||
end | |||
end | |||
elseif args.type == 'stat' then | |||
for i, data in ipairs(args.options) do | |||
local value = tpl_args._stats[data.key] | |||
if value ~= nil then | |||
base_values[i] = value.min | |||
temp_values[#temp_values+1] = {value=value, index=i} | |||
end | |||
end | |||
else | |||
for i, data in ipairs(args.options) do | |||
base_values[i] = tpl_args[data.key] | |||
local value = {} | |||
if tpl_args[data.key .. '_range_minimum'] ~= nil then | |||
value.min = tpl_args[data.key .. '_range_minimum'] | |||
value.max = tpl_args[data.key .. '_range_maximum'] | |||
elseif tpl_args[data.key] ~= nil then | |||
value.min = tpl_args[data.key] | |||
value.max = tpl_args[data.key] | |||
end | |||
if value.min == nil then | |||
else | |||
temp_values[#temp_values+1] = {value=value, index=i} | |||
end | |||
end | |||
end | |||
local final_values = {} | |||
for i, data in ipairs(temp_values) do | |||
local opt = args.options[data.index] | |||
local insert = false | |||
if opt.hide_default == nil then | |||
insert = true | |||
elseif opt.hide_default_key == nil then | |||
local v = data.value | |||
if opt.hide_default ~= v.min and opt.hide_default ~= v.max then | |||
insert = true | |||
end | |||
else | |||
local v = { | |||
min = tpl_args[opt.hide_default_key .. '_range_minimum'], | |||
max = tpl_args[opt.hide_default_key .. '_range_maximum'], | |||
} | |||
if v.min == nil or v.max == nil then | |||
if opt.hide_default ~= tpl_args[opt.hide_default_key] then | |||
insert = true | |||
end | |||
elseif opt.hide_default ~= v.min and opt.hide_default ~= v.max then | |||
insert = true | |||
end | |||
end | |||
if insert == true then | |||
table.insert(final_values, data) | |||
end | |||
end | |||
-- all zeros = dont display and return early | |||
if #final_values == 0 then | |||
return nil | |||
end | |||
local out = {} | |||
for i, data in ipairs(final_values) do | |||
local value = data.value | |||
value.base = base_values[data.index] | |||
local options = args.options[data.index] | |||
if options.color == nil and args.type == 'gem' then | |||
value.color = 'value' | |||
end | |||
out[#out+1] = h.format_value(tpl_args, frame, value, options) | |||
end | |||
if args.inline then | |||
return m_util.html.poe_color('default', string.format(args.inline, unpack(out))) | |||
else | |||
return table.concat(out, '') | |||
end | |||
end | |||
end | |||
function core.factory.display_value_only(key) | |||
return function(tpl_args, frame) | |||
return tpl_args[key] | |||
end | |||
end | |||
function core.factory.descriptor_value(args) | |||
-- Arguments: | |||
-- key | |||
-- tbl | |||
args = args or {} | |||
return function (tpl_args, frame, value) | |||
args.tbl = args.tbl or tpl_args | |||
if args.tbl[args.key] then | |||
value = m_util.html.abbr(value, args.tbl[args.key]) | |||
end | |||
return value | |||
end | |||
end | |||
function core.factory.damage_html(args) | |||
return function(tpl_args, frame) | |||
if args.key ~= 'physical' then | |||
args.color = args.key | |||
args.no_color = true | |||
end | |||
local keys = { | |||
min=args.key .. '_damage_min', | |||
max=args.key .. '_damage_max', | |||
} | |||
local value = {} | |||
for ktype, key in pairs(keys) do | |||
value[ktype] = core.factory.display_value{options={ [1] = { | |||
key = key, | |||
no_color = args.no_color, | |||
hide_default = 0 | |||
}}}(tpl_args, frame) | |||
end | |||
if value.min and value.max then | |||
value = value.min .. '–' .. value.max | |||
if args.color ~= nil then | |||
value = m_util.html.poe_color(args.color, value) | |||
end | |||
tpl_args[args.key .. '_damage_html'] = value | |||
end | |||
end | |||
end | |||
core.display = {} | |||
function core.display.add_to_container_from_map(tpl_args, frame, container, mapping) | |||
local grpcont | |||
local valid | |||
local statcont = mw.html.create('span') | |||
statcont | |||
:attr('class', 'item-stats') | |||
:done() | |||
local count = 0 | |||
for _, group in ipairs(mapping) do | |||
grpcont = {} | |||
if group.func == nil then | |||
for _, disp in ipairs(group) do | |||
valid = true | |||
-- No args to verify which means always valid | |||
if disp.args == nil then | |||
elseif type(disp.args) == 'table' then | |||
for _, key in ipairs(disp.args) do | |||
if tpl_args[key] == nil then | |||
valid = false | |||
break | |||
end | |||
end | |||
elseif type(disp.args) == 'function' then | |||
valid = disp.args(tpl_args, frame) | |||
end | |||
if valid then | |||
grpcont[#grpcont+1] = disp.func(tpl_args, frame) | |||
end | |||
end | |||
else | |||
grpcont = group.func(tpl_args, frame) | |||
end | |||
if #grpcont > 0 then | |||
count = count + 1 | |||
local header = '' | |||
if group.header == nil then | |||
elseif type(group.header) == 'function' then | |||
header = group.header() | |||
else | |||
header = string.format('<em class="header">%s</em><br>', group.header) | |||
end | |||
statcont | |||
:tag('span') | |||
:attr('class', 'group ' .. (group.css_class or '')) | |||
:wikitext(header .. table.concat(grpcont, '<br>')) | |||
:done() | |||
end | |||
end | |||
-- Don't add empty containers | |||
if count > 0 then | |||
container:node(statcont) | |||
end | |||
end | |||
-- | |||
-- argument mapping | |||
-- | |||
-- format: | |||
-- tpl_args key = { | |||
-- no_copy = true or nil -- When loading an base item, dont copy this key | |||
-- property = 'prop', -- Property associated with this key | |||
-- property_func = function or nil -- Function to unpack the property into a native lua value. | |||
-- If not specified, func is used. | |||
-- If neither is specified, value is copied as string | |||
-- func = function or nil -- Function to unpack the argument into a native lua value and validate it. | |||
-- If not specified, value will not be set. | |||
-- default = object -- Default value if the parameter is nil | |||
-- } | |||
core.map = { | |||
-- special params | |||
html = { | |||
no_copy = true, | |||
field = 'html', | |||
type = 'Text', | |||
func = nil, | |||
}, | |||
implicit_stat_text = { | |||
field = 'implicit_stat_text', | |||
type = 'Text', | |||
func = function(tpl_args, frame) | |||
tpl_args.implicit_stat_text = core.process_mod_stats(tpl_args, {type='implicit'}) | |||
end, | |||
}, | |||
explicit_stat_text = { | |||
field = 'explicit_stat_text', | |||
type = 'Text', | |||
func = function(tpl_args, frame) | |||
tpl_args.explicit_stat_text = core.process_mod_stats(tpl_args, {type='explicit'}) | |||
if tpl_args.is_talisman or tpl_args.is_corrupted then | |||
if tpl_args.explicit_stat_text == nil or tpl_args.explicit_stat_text == '' then | |||
tpl_args.explicit_stat_text = m_util.html.poe_color('corrupted', i18n.tooltips.corrupted) | |||
else | |||
tpl_args.explicit_stat_text = (tpl_args.explicit_stat_text or '') .. '<br>' .. m_util.html.poe_color('corrupted', i18n.tooltips.corrupted) | |||
end | |||
end | |||
end, | |||
}, | |||
stat_text = { | |||
field = 'stat_text', | |||
type = 'Text', | |||
func = function(tpl_args, frame) | |||
local sep = '' | |||
if tpl_args.implicit_stat_text and tpl_args.explicit_stat_text then | |||
sep = string.format('<span class="item-stat-separator -%s"></span>', tpl_args.frame_type) | |||
end | |||
local text = (tpl_args.implicit_stat_text or '') .. sep .. (tpl_args.explicit_stat_text or '') | |||
if string.len(text) > 0 then | |||
tpl_args.stat_text = text | |||
end | |||
end, | |||
}, | |||
-- processed in core.build_item_classes | |||
class = { | |||
no_copy = true, | |||
field = 'class', | |||
type = 'String', | |||
func = m_util.cast.factory.table('class', {key='full', tbl=m_game.constants.item.class}), | |||
}, | |||
-- generic | |||
rarity = { | |||
no_copy = true, | |||
field = 'rarity', | |||
type = 'String', | |||
func = m_util.cast.factory.table('rarity', {key={'full', 'long_lower'}, tbl=m_game.constants.item.rarity, rtrkey='full'}), | |||
}, | |||
name = { | |||
no_copy = true, | |||
field = 'name', | |||
type = 'String', | |||
func = nil, | |||
}, | |||
size_x = { | |||
field = 'size_x', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('size_x'), | |||
}, | |||
size_y = { | |||
field = 'size_y', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('size_y'), | |||
}, | |||
drop_enabled = { | |||
no_copy = true, | |||
field = 'drop_enabled', | |||
type = 'Boolean', | |||
func = m_util.cast.factory.boolean('drop_enabled'), | |||
default = true, | |||
}, | |||
drop_level = { | |||
no_copy = true, | |||
field = 'drop_level', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('drop_level'), | |||
}, | |||
drop_level_maximum = { | |||
no_copy = true, | |||
field = 'drop_level_maximum', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('drop_level_maximum'), | |||
}, | |||
drop_leagues = { | |||
no_copy = true, | |||
field = 'drop_leagues', | |||
type = 'List (,) of String', | |||
func = m_util.cast.factory.assoc_table('drop_leagues', {tbl=m_game.constants.leagues, errmsg=i18n.errors.invalid_league}), | |||
}, | |||
drop_areas = { | |||
no_copy = true, | |||
field = 'drop_areas', | |||
type = 'List (,) of String', | |||
func = function(tpl_args, frame) | |||
if tpl_args.drop_areas == nil then | |||
return | |||
end | |||
tpl_args.drop_areas = m_util.string.split(tpl_args.drop_areas, ',%s*') | |||
tpl_args.drop_areas_data = m_util.cargo.array_query{ | |||
tables={'areas'}, | |||
fields={'areas._pageName', 'areas.name', 'areas.main_page'}, | |||
id_field='areas.id', | |||
id_array=tpl_args.drop_areas, | |||
} | |||
-- TODO: Cargo: Raise error on missing | |||
local areas = {} | |||
for _, row in pairs(tpl_args.drop_areas) do | |||
areas[#areas+1] = row['area.name'] | |||
end | |||
end, | |||
}, | |||
drop_text = { | |||
no_copy = true, | |||
field = 'drop_text', | |||
type = 'String', | |||
}, | |||
required_level = { | |||
field = 'required_level_base', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('required_level'), | |||
default = 1, | |||
}, | |||
required_level_final = { | |||
field = 'required_level', | |||
type = 'Integer', | |||
func = function(tpl_args, frame) | |||
tpl_args.required_level_final = tpl_args.required_level | |||
end, | |||
default = 1, | |||
}, | |||
required_dexterity = { | |||
field = 'required_dexterity', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('required_dexterity'), | |||
default = 0, | |||
}, | |||
required_strength = { | |||
field = 'required_strength', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('required_strength'), | |||
default = 0, | |||
}, | |||
required_intelligence = { | |||
field = 'required_intelligence', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('required_intelligence'), | |||
default = 0, | |||
}, | |||
inventory_icon = { | |||
no_copy = true, | |||
field = 'inventory_icon', | |||
type = 'String', | |||
func = function(tpl_args, frame) | |||
if tpl_args.class == 'Divination Card' then | |||
tpl_args.inventory_icon = tpl_args.inventory_icon or 'Divination card' | |||
end | |||
tpl_args.inventory_icon_id = tpl_args.inventory_icon or tpl_args.name | |||
tpl_args.inventory_icon = string.format(i18n.inventory_icon, tpl_args.inventory_icon_id) | |||
end, | |||
}, | |||
-- note: this must be called after inventory item to work correctly as it depends on tpl_args.inventory_icon_id being set | |||
alternate_art_inventory_icons = { | |||
no_copy = true, | |||
field = 'alternate_art_inventory_icons', | |||
type = 'List (,) of String', | |||
func = function(tpl_args, frame) | |||
local icons = {} | |||
if tpl_args.alternate_art_inventory_icons ~= nil then | |||
local names = m_util.string.split(tpl_args.alternate_art_inventory_icons, ',%s*') | |||
for _, name in ipairs(names) do | |||
icons[#icons+1] = string.format(i18n.inventory_icon, string.format('%s %s', tpl_args.inventory_icon_id, name)) | |||
end | |||
end | |||
tpl_args.alternate_art_inventory_icons = icons | |||
end, | |||
default = function () return {} end, | |||
}, | |||
cannot_be_traded_or_modified = { | |||
no_copy = true, | |||
field = 'cannot_be_traded_or_modified', | |||
type = 'Boolean', | |||
func = m_util.cast.factory.boolean('cannot_be_traded_or_modified'), | |||
default = false, | |||
}, | |||
help_text = { | |||
field = 'help_text', | |||
type = 'Text', | |||
func = nil, | |||
}, | |||
flavour_text = { | |||
no_copy = true, | |||
field = 'flavour_text', | |||
type = 'Text', | |||
func = nil, | |||
}, | |||
tags = { | |||
field = 'tags', | |||
type = 'List (,) of String', | |||
func = m_util.cast.factory.assoc_table('tags', { | |||
tbl = m_game.constants.tags, | |||
errmsg = i18n.errors.invalid_tag, | |||
}), | |||
}, | |||
metadata_id = { | |||
no_copy = true, | |||
field = 'metadata_id', | |||
type = 'String', | |||
func = nil, | |||
}, | |||
is_corrupted = { | |||
no_copy = true, | |||
field = 'is_corrupted', | |||
type = 'Boolean', | |||
func = m_util.cast.factory.boolean('is_corrupted'), | |||
default = false, | |||
}, | |||
is_relic = { | |||
no_copy = true, | |||
field = 'is_relic', | |||
type = 'Boolean', | |||
func = function(tpl_args, frame) | |||
m_util.cast.factory.boolean('is_relic')(tpl_args, frame) | |||
if tpl_args.is_relic == true and tpl_args.rarity ~= 'Unique' then | |||
error(i18n.errors.non_unique_relic) | |||
end | |||
end, | |||
default = false, | |||
}, | |||
purchase_costs = { | |||
func = function(tpl_args, frame) | |||
local purchase_costs = {} | |||
for _, rarity_names in ipairs(m_game.constants.item.rarity) do | |||
local rtbl = {} | |||
local prefix = string.format('purchase_cost_%s', rarity_names.long_lower) | |||
local i = 1 | |||
while i ~= -1 do | |||
prefix = prefix .. i | |||
local values = { | |||
name = tpl_args[prefix .. '_name'], | |||
amount = tonumber(tpl_args[prefix .. '_amount']), | |||
rarity = rarity_names.long_upper, | |||
} | |||
if values.name ~= nil and values.amount ~= nil then | |||
rtbl[#rtbl+1] = values | |||
i = i + 1 | |||
tpl_args._subobjects[#tpl_args._subobjects+1] = { | |||
_table = 'item_purchase_costs', | |||
amount = values.amount, | |||
name = values.name, | |||
rarity = values.rarity, | |||
} | |||
else | |||
i = -1 | |||
end | |||
end | |||
purchase_costs[rarity_names.long_lower] = rtbl | |||
end | |||
tpl_args.purchase_costs = purchase_costs | |||
end, | |||
func_fetch = function(tpl_args, frame) | |||
if tpl_args.rarity ~= 'Unique' then | |||
return | |||
end | |||
local results = cargo.query( | |||
'items, item_purchase_costs', | |||
'item_purchase_costs.amount=item_purchase_costs.amount,item_purchase_costs.name=item_purchase_costs.name,item_purchase_costs.rarity=item_purchase_costs.rarity', | |||
{ | |||
join = 'items._pageID=item_purchase_costs._pageID', | |||
where = string.format('items._pageName="%s" AND item_purchase_costs.rarity="Unique"', tpl_args.base_item_page), | |||
-- Workaround: Fix cargo duplicates | |||
groupBy = 'item_purchase_costs._pageName, item_purchase_costs.name, item_purchase_costs.rarity', | |||
} | |||
) | |||
for _, row in ipairs(results) do | |||
local values = { | |||
rarity = row['item_purchase_costs.rarity'], | |||
name = row['item_purchase_costs.name'], | |||
amount = tonumber(row['item_purchase_costs.amount']), | |||
} | |||
local datavar = tpl_args.purchase_costs[string.lower(values.rarity)] | |||
datavar[#datavar+1] = values | |||
tpl_args._subobjects[#tpl_args._subobjects+1] = { | |||
_table = 'item_purchase_costs', | |||
amount = values.amount, | |||
name = values.name, | |||
rarity = values.rarity, | |||
} | |||
end | |||
end, | |||
}, | |||
-- | |||
-- specific section | |||
-- | |||
-- Most item classes | |||
quality = { | |||
no_copy = true, | |||
field = 'quality', | |||
type = 'Integer', | |||
-- Can be set manually, but default to Q20 for unique weapons/body armours | |||
-- Also must copy to stat for the stat adjustments to work properly | |||
func = function(tpl_args, frame) | |||
local quality = tonumber(tpl_args.quality) | |||
-- | |||
if quality == nil then | |||
if tpl_args.rarity ~= 'Unique' then | |||
quality = 0 | |||
elseif core.class_groups.weapons.keys[tpl_args.class] or core.class_groups.armor.keys[tpl_args.class] then | |||
quality = 20 | |||
else | |||
quality = 0 | |||
end | |||
end | |||
tpl_args.quality = quality | |||
local stat = { | |||
min = quality, | |||
max = quality, | |||
avg = quality, | |||
} | |||
h.stats_update(tpl_args, 'quality', stat, nil, '_stats') | |||
if tpl_args.class == 'Utility Flasks' or tpl_args.class == 'Critical Utility Flasks' then | |||
h.stats_update(tpl_args, 'quality_flask_duration', stat, nil, '_stats') | |||
-- quality is added to quantity for maps | |||
elseif tpl_args.class == 'Maps' then | |||
h.stats_update(tpl_args, 'map_item_drop_quantity_+%', stat, nil, '_stats') | |||
end | |||
end, | |||
}, | |||
-- amulets | |||
is_talisman = { | |||
field = 'is_talisman', | |||
type = 'Boolean', | |||
func = m_util.cast.factory.boolean('is_talisman'), | |||
default = false, | |||
}, | |||
talisman_tier = { | |||
field = 'talisman_tier', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('talisman_tier'), | |||
}, | |||
-- flasks | |||
charges_max = { | |||
field = 'charges_max', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('charges_max'), | |||
}, | |||
charges_per_use = { | |||
field = 'charges_per_use', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('charges_per_use'), | |||
}, | |||
flask_mana = { | |||
field = 'mana', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('flask_mana'), | |||
}, | |||
flask_life = { | |||
field = 'life', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('flask_life'), | |||
}, | |||
flask_duration = { | |||
field = 'duration', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('flask_duration'), | |||
}, | |||
buff_id = { | |||
field = 'id', | |||
type = 'String', | |||
func = nil, | |||
}, | |||
buff_values = { | |||
field = 'values', | |||
type = 'List (,) of Integer', | |||
func = function(tpl_args, frame) | |||
local values = {} | |||
local i = 0 | |||
repeat | |||
i = i + 1 | |||
local key = 'buff_value' .. i | |||
values[i] = tonumber(tpl_args[key]) | |||
tpl_args[key] = nil | |||
until values[i] == nil | |||
tpl_args.buff_values = values | |||
end, | |||
}, | |||
buff_stat_text = { | |||
field = 'stat_text', | |||
type = 'String', | |||
func = nil, | |||
}, | |||
buff_icon = { | |||
field = 'icon', | |||
type = 'String', | |||
func = function(tpl_args, frame) | |||
tpl_args.buff_icon = string.format(i18n.status_icon, tpl_args.name) | |||
end, | |||
}, | |||
-- weapons | |||
critical_strike_chance = { | |||
field = 'critical_strike_chance', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('critical_strike_chance'), | |||
}, | |||
attack_speed = { | |||
field = 'attack_speed', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('attack_speed'), | |||
}, | |||
range = { | |||
field = 'range', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('range'), | |||
}, | |||
physical_damage_min = { | |||
field = 'physical_damage_min', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('physical_damage_min'), | |||
}, | |||
physical_damage_max = { | |||
field = 'physical_damage_max', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('physical_damage_max'), | |||
}, | |||
fire_damage_min = { | |||
field = 'fire_damage_min', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('fire_damage_min'), | |||
default = 0, | |||
}, | |||
fire_damage_max = { | |||
field = 'fire_damage_max', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('fire_damage_max'), | |||
default = 0, | |||
}, | |||
cold_damage_min = { | |||
field = 'cold_damage_min', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('cold_damage_min'), | |||
default = 0, | |||
}, | |||
cold_damage_max = { | |||
field = 'cold_damage_max', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('cold_damage_max'), | |||
default = 0, | |||
}, | |||
lightning_damage_min = { | |||
field = 'lightning_damage_min', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('lightning_damage_min'), | |||
default = 0, | |||
}, | |||
lightning_damage_max = { | |||
field = 'lightning_damage_max', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('lightning_damage_max'), | |||
default = 0, | |||
}, | |||
chaos_damage_min = { | |||
field = 'chaos_damage_min', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('chaos_damage_min'), | |||
default = 0, | |||
}, | |||
chaos_damage_max = { | |||
field = 'chaos_damage_max', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('chaos_damage_max'), | |||
default = 0, | |||
}, | |||
-- armor-type stuff | |||
armour = { | |||
field = 'armour', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('armour'), | |||
default = 0, | |||
}, | |||
energy_shield = { | |||
field = 'energy_shield', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('energy_shield'), | |||
default = 0, | |||
}, | |||
evasion = { | |||
field = 'evasion', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('evasion'), | |||
default = 0, | |||
}, | |||
-- shields | |||
block = { | |||
field = 'block', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('block'), | |||
}, | |||
-- skill gem stuff | |||
gem_description = { | |||
field = 'gem_description', | |||
type = 'Text', | |||
func = nil, | |||
}, | |||
dexterity_percent = { | |||
field = 'dexterity_percent', | |||
type = 'Integer', | |||
func = m_util.cast.factory.percentage('dexterity_percent'), | |||
}, | |||
strength_percent = { | |||
field = 'strength_percent', | |||
type = 'Integer', | |||
func = m_util.cast.factory.percentage('strength_percent'), | |||
}, | |||
intelligence_percent = { | |||
field = 'intelligence_percent', | |||
type = 'Integer', | |||
func = m_util.cast.factory.percentage('intelligence_percent'), | |||
}, | |||
primary_attribute = { | |||
field = 'primary_attribute', | |||
type = 'String', | |||
func = function(tpl_args, frame) | |||
for _, attr in ipairs(m_game.constants.attributes) do | |||
local val = tpl_args[attr.long_lower .. '_percent'] | |||
if val and val >= 60 then | |||
tpl_args['primary_attribute'] = attr.long_upper | |||
return | |||
end | |||
end | |||
tpl_args['primary_attribute'] = 'None' | |||
end, | |||
}, | |||
gem_tags = { | |||
field = 'gem_tags', | |||
type = 'List (,) of String', | |||
-- TODO: default rework | |||
func = m_util.cast.factory.array_table('gem_tags', { | |||
tbl = m_game.constants.item.gem_tags, | |||
errmsg = i18n.errors.invalid_tag, | |||
}), | |||
default = function () return {} end, | |||
}, | |||
skill_screenshot = { | |||
field = 'skill_screenshot', | |||
type = 'Page', | |||
func = function(tpl_args, frame) | |||
tpl_args.skill_screenshot = string.format(i18n.skill_screenshot, tpl_args.name) | |||
end, | |||
}, | |||
-- Support gems only | |||
support_gem_letter = { | |||
field = 'support_gem_letter', | |||
type = 'String(size=1)', | |||
func = nil, | |||
}, | |||
support_gem_letter_html = { | |||
field = 'support_gem_letter_html', | |||
type = 'Text', | |||
func = function(tpl_args, frame) | |||
if tpl_args.support_gem_letter == nil then | |||
return | |||
end | |||
-- TODO replace this with a loop possibly | |||
local css_map = { | |||
strength = 'red', | |||
intelligence = 'blue', | |||
dexterity = 'green', | |||
} | |||
local id | |||
for k, v in pairs(css_map) do | |||
k = string.format('%s_percent', k) | |||
if tpl_args[k] and tpl_args[k] > 50 then | |||
id = v | |||
break | |||
end | |||
end | |||
if id ~= nil then | |||
local container = mw.html.create('span') | |||
container | |||
:attr('class', string.format('support-gem-id-%s', id)) | |||
:wikitext(tpl_args.support_gem_letter) | |||
:done() | |||
tpl_args.support_gem_letter_html = tostring(container) | |||
end | |||
end, | |||
}, | |||
-- Maps | |||
map_tier = { | |||
field = 'tier', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('tier'), | |||
}, | |||
map_guild_character = { | |||
field = 'guild_character', | |||
type = 'String(size=1)', | |||
func = nil, | |||
}, | |||
map_area_id = { | |||
field = 'area_id', | |||
type = 'String', | |||
func = nil, -- TODO: Validate against a query? | |||
}, | |||
map_area_level = { | |||
field = 'area_level', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('map_area_level'), | |||
}, | |||
unique_map_guild_character = { | |||
ield = 'guild_character', | |||
type = 'String(size=1)', | |||
func_copy = function(tpl_args, frame) | |||
tpl_args.map_guild_character = tpl_args.unique_map_guild_character | |||
end, | |||
func = nil, | |||
}, | |||
unique_map_area_id = { | |||
field = 'unique_area_id', | |||
type = 'String', | |||
func = nil, -- TODO: Validate against a query? | |||
func_copy = function(tpl_args, frame) | |||
tpl_args.map_area_id = tpl_args.unique_map_area_id | |||
end, | |||
}, | |||
unique_map_area_level = { | |||
field = 'unique_area_level', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('unique_map_area_level'), | |||
func_copy = function(tpl_args, frame) | |||
tpl_args.map_area_level = tpl_args.unique_map_area_level | |||
end, | |||
}, | |||
-- | |||
-- Currency-like items | |||
-- | |||
stack_size = { | |||
field = 'stack_size', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('stack_size'), | |||
}, | |||
stack_size_currency_tab = { | |||
field = 'stack_size_currency_tab', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('stack_size_currency_tab'), | |||
}, | |||
description = { | |||
field = 'description', | |||
type = 'Text', | |||
func = nil, | |||
}, | |||
cosmetic_type = { | |||
field = 'cosmetic_type', | |||
type = 'String', | |||
func = nil, | |||
}, | |||
-- for essences | |||
is_essence = { | |||
field = nil, | |||
func = m_util.cast.factory.boolean('is_essence'), | |||
default = false, | |||
}, | |||
essence_level_restriction = { | |||
field = 'level_restriction', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('essence_level_restriction'), | |||
}, | |||
essence_level = { | |||
field = 'level', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('essence_level'), | |||
}, | |||
-- | |||
-- hideout doodads (HideoutDoodads.dat) | |||
-- | |||
is_master_doodad = { | |||
field = 'is_master_doodad', | |||
type = 'Boolean', | |||
func = m_util.cast.factory.boolean('is_master_doodad'), | |||
}, | |||
master = { | |||
field = 'master', | |||
type = 'String', | |||
-- todo validate against list of master names | |||
func = m_util.cast.factory.table('master', {key='full', tbl=m_game.constants.masters}), | |||
}, | |||
master_level_requirement = { | |||
field = 'level_requirement', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('master_level_requirement'), | |||
}, | |||
master_favour_cost = { | |||
field = 'favour_cost', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('master_favour_cost'), | |||
}, | |||
variation_count = { | |||
field = 'variation_count', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('variation_count'), | |||
}, | |||
-- Propehcy | |||
prophecy_id = { | |||
field = 'prophecy_id', | |||
type = 'String', | |||
func = nil, | |||
}, | |||
prediction_text = { | |||
field = 'prediction_text', | |||
type = 'Text', | |||
func = nil, | |||
}, | |||
seal_cost = { | |||
field = 'seal_cost', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('seal_cost'), | |||
}, | |||
prophecy_reward = { | |||
field = 'reward', | |||
type = 'Text', | |||
func = nil, | |||
}, | |||
prophecy_objective = { | |||
field = 'objective', | |||
type = 'Text', | |||
func = nil, | |||
}, | |||
-- Divination cards | |||
card_art = { | |||
field = 'card_art', | |||
type = 'Page', | |||
func = function(tpl_args, frame) | |||
tpl_args.card_art = string.format(i18n.divination_card_art, tpl_args.name) | |||
end, | |||
}, | |||
-- ------------------------------------------------------------------------ | |||
-- derived stats | |||
-- ------------------------------------------------------------------------ | |||
-- For rarity != normal, rarity already verified | |||
base_item = { | |||
no_copy = true, | |||
field = 'base_item', | |||
type = 'String', | |||
func = function(tpl_args, frame) | |||
tpl_args.base_item = tpl_args.base_item_data['items.name'] | |||
end, | |||
}, | |||
base_item_id = { | |||
no_copy = true, | |||
field = 'base_item_id', | |||
type = 'String', | |||
func = function(tpl_args, frame) | |||
tpl_args.base_item_id = tpl_args.base_item_data['items.metadata_id'] | |||
end, | |||
}, | |||
base_item_page = { | |||
no_copy = true, | |||
field = 'base_item_page', | |||
type = 'Page', | |||
func = function(tpl_args, frame) | |||
tpl_args.base_item_page = tpl_args.base_item_data['items._pageName'] | |||
end, | |||
}, | |||
name_list = { | |||
no_copy = true, | |||
field = 'name_list', | |||
type = 'List (,) of String', | |||
func = function(tpl_args, frame) | |||
if tpl_args.name_list ~= nil then | |||
tpl_args.name_list = m_util.string.split(tpl_args.name_list, ',%s*') | |||
tpl_args.name_list[#tpl_args.name_list+1] = tpl_args.name | |||
else | |||
tpl_args.name_list = {tpl_args.name} | |||
end | |||
end, | |||
}, | |||
frame_type = { | |||
no_copy = true, | |||
field = 'frame_type', | |||
type = 'String', | |||
property = nil, | |||
func = function(tpl_args, frame) | |||
if tpl_args.name == 'Prophecy' or tpl_args.base_item == 'Prophecy' then | |||
tpl_args.frame_type = 'prophecy' | |||
return | |||
end | |||
local var = core.class_specifics[tpl_args.class] | |||
if var ~= nil and var.frame_type ~= nil then | |||
tpl_args.frame_type = var.frame_type | |||
return | |||
end | |||
if tpl_args.is_relic then | |||
tpl_args.frame_type = 'relic' | |||
return | |||
end | |||
tpl_args.frame_type = string.lower(tpl_args.rarity) | |||
end, | |||
}, | |||
-- | |||
-- args populated by mod validation | |||
-- | |||
mods = { | |||
no_copy = true, | |||
field = 'mods', | |||
type = 'List (,) of String', | |||
default = function () return {} end, | |||
}, | |||
implicit_mods = { | |||
field = 'implicit_mods', | |||
type = 'List (,) of String', | |||
func_copy = function (tpl_args) | |||
tpl_args.implicit_mods = m_util.string.split(tpl_args.implicit_mods, ',%s*') | |||
for _, modid in ipairs(tpl_args.implicit_mods) do | |||
tpl_args.mods[#tpl_args.mods+1] = modid | |||
tpl_args._mods[#tpl_args._mods+1] = { | |||
result=nil, | |||
modid=modid, | |||
type='implicit', | |||
} | |||
end | |||
end, | |||
default = function () return {} end, | |||
}, | |||
explicit_mods = { | |||
field = 'explicit_mods', | |||
type = 'List (,) of String', | |||
default = function () return {} end, | |||
}, | |||
physical_damage_html = { | |||
no_copy = true, | |||
field = 'physical_damage_html', | |||
type = 'Text', | |||
func = core.factory.damage_html{key='physical'}, | |||
}, | |||
fire_damage_html = { | |||
no_copy = true, | |||
field = 'fire_damage_html', | |||
type = 'Text', | |||
func = core.factory.damage_html{key='fire'}, | |||
}, | |||
cold_damage_html = { | |||
no_copy = true, | |||
field = 'cold_damage_html', | |||
type = 'Text', | |||
func = core.factory.damage_html{key='cold'}, | |||
}, | |||
lightning_damage_html = { | |||
no_copy = true, | |||
field = 'lightning_damage_html', | |||
type = 'Text', | |||
func = core.factory.damage_html{key='lightning'}, | |||
}, | |||
chaos_damage_html = { | |||
no_copy = true, | |||
field = 'chaos_damage_html', | |||
type = 'Text', | |||
func = core.factory.damage_html{key='chaos'}, | |||
}, | |||
damage_avg = { | |||
no_copy = true, | |||
field = 'damage_avg', | |||
type = 'Text', | |||
func = function(tpl_args, frame) | |||
local dmg = {min=0, max=0} | |||
for key, _ in pairs(dmg) do | |||
for _, data in ipairs(m_game.constants.damage_types) do | |||
dmg[key] = dmg[key] + tpl_args[string.format('%s_damage_%s_range_average', data.short_lower, key)] | |||
end | |||
end | |||
dmg = (dmg.min + dmg.max) / 2 | |||
tpl_args.damage_avg = dmg | |||
end, | |||
}, | |||
damage_html = { | |||
no_copy = true, | |||
field = 'damage_html', | |||
type = 'Text', | |||
func = function(tpl_args, frame) | |||
local text = {} | |||
for _, data in ipairs(m_game.constants.damage_types) do | |||
local value = tpl_args[data.short_lower .. '_damage_html'] | |||
if value ~= nil then | |||
text[#text+1] = value | |||
end | |||
end | |||
if #text > 0 then | |||
tpl_args.damage_html = table.concat(text, '<br>') | |||
end | |||
end, | |||
}, | |||
item_limit = { | |||
no_copy = true, | |||
field = 'item_limit', | |||
type = 'Integer', | |||
func = m_util.cast.factory.number('item_limit'), | |||
}, | |||
jewel_radius_html = { | |||
no_copy = true, | |||
field = 'radius_html', | |||
type = 'Text', | |||
func = function(tpl_args, frame) | |||
local radius = tpl_args._stats.local_jewel_effect_base_radius | |||
if radius then | |||
radius = radius.min | |||
tpl_args.jewel_radius_html = string.format('%s (%i)', (m_game.constants.item.jewel_radius_to_size[radius] or '?'), radius) | |||
end | |||
end, | |||
}, | |||
drop_areas_html = { | |||
no_copy = true, | |||
field = 'drop_areas_html', | |||
type = 'Text', | |||
func = function(tpl_args, frame) | |||
if tpl_args.drop_areas_data == nil then | |||
return | |||
end | |||
if tpl_args.drop_areas_html ~= nil then | |||
return | |||
end | |||
local areas = {} | |||
for _, data in pairs(tpl_args.drop_areas_data) do | |||
local page | |||
if data['areas.main_page'] == '' then | |||
page = data['areas._pageName'] | |||
else | |||
page = data['areas.main_page'] | |||
end | |||
areas[#areas+1] = string.format('[[%s]]', page) | |||
end | |||
tpl_args.drop_areas_html = table.concat(areas, ', ') | |||
end, | |||
}, | |||
} | |||
core.stat_map = { | |||
required_level_final = { | |||
field = 'required_level', | |||
stats_add = { | |||
'local_level_requirement_+', | |||
}, | |||
stats_override = { | |||
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=1, max=1}, | |||
}, | |||
minimum = 1, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
range = { | |||
field = 'range', | |||
stats_add = { | |||
'local_weapon_range_+', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
physical_damage_min = { | |||
field = 'physical_damage_min', | |||
stats_add = { | |||
'local_minimum_added_physical_damage', | |||
}, | |||
stats_increased = { | |||
'local_physical_damage_+%', | |||
'quality', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
physical_damage_max = { | |||
field = 'physical_damage_max', | |||
stats_add = { | |||
'local_maximum_added_physical_damage', | |||
}, | |||
stats_increased = { | |||
'local_physical_damage_+%', | |||
'quality', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
fire_damage_min = { | |||
field = 'fire_damage_min', | |||
stats_add = { | |||
'local_minimum_added_fire_damage', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
color = 'fire', | |||
fmt = '%i', | |||
}, | |||
}, | |||
fire_damage_max = { | |||
field = 'fire_damage_max', | |||
stats_add = { | |||
'local_maximum_added_fire_damage', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
color = 'fire', | |||
fmt = '%i', | |||
}, | |||
}, | |||
cold_damage_min = { | |||
field = 'cold_damage_min', | |||
stats_add = { | |||
'local_minimum_added_cold_damage', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
color = 'cold', | |||
fmt = '%i', | |||
}, | |||
}, | |||
cold_damage_max = { | |||
field = 'cold_damage_max', | |||
stats_add = { | |||
'local_maximum_added_cold_damage', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
color = 'cold', | |||
fmt = '%i', | |||
}, | |||
}, | |||
lightning_damage_min = { | |||
field = 'lightning_damage_min', | |||
stats_add = { | |||
'local_minimum_added_lightning_damage', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
color = 'lightning', | |||
fmt = '%i', | |||
}, | |||
}, | |||
lightning_damage_max = { | |||
field = 'lightning_damage_max', | |||
stats_add = { | |||
'local_maximum_added_lightning_damage', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
color = 'lightning', | |||
fmt = '%i', | |||
}, | |||
}, | |||
chaos_damage_min = { | |||
field = 'chaos_damage_min', | |||
stats_add = { | |||
'local_minimum_added_chaos_damage', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
color = 'chaos', | |||
fmt = '%i', | |||
}, | |||
}, | |||
chaos_damage_max = { | |||
field = 'chaos_damage_max', | |||
stats_add = { | |||
'local_maximum_added_chaos_damage', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
color = 'chaos', | |||
fmt = '%i', | |||
}, | |||
}, | |||
critical_strike_chance = { | |||
field = 'critical_strike_chance', | |||
stats_add = { | |||
'local_critical_strike_chance', | |||
}, | |||
stats_increased = { | |||
'local_critical_strike_chance_+%', | |||
}, | |||
stats_override = { | |||
['local_weapon_always_crit'] = {min=100, max=100}, | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%.2f%%', | |||
}, | |||
}, | |||
attack_speed = { | |||
field = 'attack_speed', | |||
stats_increased = { | |||
'local_attack_speed_+%', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%.2f', | |||
}, | |||
}, | |||
flask_life = { | |||
field = 'life', | |||
stats_add = { | |||
'local_flask_life_to_recover', | |||
}, | |||
stats_increased = { | |||
'local_flask_life_to_recover_+%', | |||
'local_flask_amount_to_recover_+%', | |||
'quality', | |||
}, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
flask_mana = { | |||
field = 'mana', | |||
stats_add = { | |||
'local_flask_mana_to_recover', | |||
}, | |||
stats_increased = { | |||
'local_flask_mana_to_recover_+%', | |||
'local_flask_amount_to_recover_+%', | |||
'quality', | |||
}, | |||
}, | |||
flask_duration = { | |||
field = 'duration', | |||
stats_increased = { | |||
'local_flask_duration_+%', | |||
-- regular quality isn't used here because it doesn't increase duration of life/mana/hybrid flasks | |||
'quality_flask_duration', | |||
}, | |||
stats_increased_inverse = { | |||
'local_flask_recovery_speed_+%', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%.2f', | |||
}, | |||
}, | |||
charges_per_use = { | |||
field = 'charges_per_use', | |||
stats_increased = { | |||
'local_charges_used_+%', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
charges_max = { | |||
field = 'charges_max', | |||
stats_add = { | |||
'local_extra_max_charges', | |||
}, | |||
stats_increased = { | |||
'local_max_charges_+%', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
block = { | |||
field = 'block', | |||
stats_add = { | |||
'local_additional_block_chance_%', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i%%', | |||
}, | |||
}, | |||
armour = { | |||
field = 'armour', | |||
stats_add = { | |||
'local_base_physical_damage_reduction_rating', | |||
}, | |||
stats_increased = { | |||
'local_physical_damage_reduction_rating_+%', | |||
'local_armour_and_energy_shield_+%', | |||
'local_armour_and_evasion_+%', | |||
'local_armour_and_evasion_and_energy_shield_+%', | |||
'quality', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
evasion = { | |||
field = 'evasion', | |||
stats_add = { | |||
'local_base_evasion_rating', | |||
}, | |||
stats_increased = { | |||
'local_evasion_rating_+%', | |||
'local_evasion_and_energy_shield_+%', | |||
'local_armour_and_evasion_+%', | |||
'local_armour_and_evasion_and_energy_shield_+%', | |||
'quality', | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
energy_shield = { | |||
field = 'energy_shield', | |||
stats_add = { | |||
'local_energy_shield' | |||
}, | |||
stats_increased = { | |||
'local_energy_shield_+%', | |||
'local_armour_and_energy_shield_+%', | |||
'local_evasion_and_energy_shield_+%', | |||
'local_armour_and_evasion_and_energy_shield_+%', | |||
'quality', | |||
}, | |||
stats_override = { | |||
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0}, | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
required_dexterity = { | |||
field = 'required_dexterity', | |||
stats_add = { | |||
'local_dexterity_requirement_+' | |||
}, | |||
stats_increased = { | |||
'local_dexterity_requirement_+%', | |||
'local_attribute_requirements_+%', | |||
}, | |||
stats_override = { | |||
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0}, | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
required_intelligence = { | |||
field = 'required_intelligence', | |||
stats_add = { | |||
'local_intelligence_requirement_+' | |||
}, | |||
stats_increased = { | |||
'local_intelligence_requirement_+%', | |||
'local_attribute_requirements_+%', | |||
}, | |||
stats_override = { | |||
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0}, | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
required_strength = { | |||
field = 'required_strength', | |||
stats_add = { | |||
'local_strength_requirement_+' | |||
}, | |||
stats_increased = { | |||
'local_strength_requirement_+%', | |||
'local_attribute_requirements_+%', | |||
}, | |||
stats_override = { | |||
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0}, | |||
}, | |||
minimum = 0, | |||
html_fmt_options = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
map_area_level = { | |||
field = 'map_area_level', | |||
stats_override = { | |||
['map_item_level_override'] = true, | |||
}, | |||
}, | |||
} | |||
core.dps_map = { | |||
{ | |||
name = 'physical_dps', | |||
field = 'physical_dps', | |||
damage_args = {'physical_damage', }, | |||
label = i18n.item_table.physical_dps, | |||
label_infobox = i18n.tooltips.physical_dps, | |||
html_fmt_options = { | |||
color = 'value', | |||
fmt = '%.1f', | |||
}, | |||
}, | |||
{ | |||
name = 'fire_dps', | |||
field = 'fire_dps', | |||
damage_args = {'fire_damage'}, | |||
label = i18n.item_table.fire_dps, | |||
label_infobox = i18n.tooltips.fire_dps, | |||
html_fmt_options = { | |||
color = 'fire', | |||
fmt = '%.1f', | |||
}, | |||
}, | |||
{ | |||
name = 'cold_dps', | |||
field = 'cold_dps', | |||
damage_args = {'cold_damage'}, | |||
label = i18n.item_table.cold_dps, | |||
label_infobox = i18n.tooltips.cold_dps, | |||
html_fmt_options = { | |||
color = 'cold', | |||
fmt = '%.1f', | |||
}, | |||
}, | |||
{ | |||
name = 'lightning_dps', | |||
field = 'lightning_dps', | |||
damage_args = {'lightning_damage'}, | |||
label = i18n.item_table.lightning_dps, | |||
label_infobox = i18n.tooltips.lightning_dps, | |||
html_fmt_options = { | |||
color = 'lightning', | |||
fmt = '%.1f', | |||
}, | |||
}, | |||
{ | |||
name = 'chaos_dps', | |||
field = 'chaos_dps', | |||
damage_args = {'chaos_damage'}, | |||
label = i18n.item_table.chaos_dps, | |||
label_infobox = i18n.tooltips.chaos_dps, | |||
html_fmt_options = { | |||
color = 'chaos', | |||
fmt = '%.1f', | |||
}, | |||
}, | |||
{ | |||
name = 'elemental_dps', | |||
field = 'elemental_dps', | |||
damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'}, | |||
label = i18n.item_table.elemental_dps, | |||
label_infobox = i18n.tooltips.elemental_dps, | |||
html_fmt_options = { | |||
color = 'value', | |||
fmt = '%.1f', | |||
}, | |||
}, | |||
{ | |||
name = 'poison_dps', | |||
field = 'poison_dps', | |||
damage_args = {'physical_damage', 'chaos_damage'}, | |||
label = i18n.item_table.poison_dps, | |||
label_infobox = i18n.tooltips.poison_dps, | |||
html_fmt_options = { | |||
color = 'value', | |||
fmt = '%.1f', | |||
}, | |||
}, | |||
{ | |||
name = 'dps', | |||
field = 'dps', | |||
damage_args = {'physical_damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'}, | |||
label = i18n.item_table.dps, | |||
label_infobox = i18n.tooltips.dps, | |||
html_fmt_options = { | |||
color = 'value', | |||
fmt = '%.1f', | |||
}, | |||
}, | |||
} | |||
core.cargo = {} | |||
core.cargo.items = { | |||
table = 'items', | |||
fields = { | |||
html = core.map.html, | |||
implicit_stat_text = core.map.implicit_stat_text, | |||
explicit_stat_text = core.map.explicit_stat_text, | |||
stat_text = core.map.stat_text, | |||
class = core.map.class, | |||
rarity = core.map.rarity, | |||
name = core.map.name, | |||
size_x = core.map.size_x, | |||
size_y = core.map.size_y, | |||
drop_enabled = core.map.drop_enabled, | |||
drop_level = core.map.drop_level, | |||
drop_level_maximum = core.map.drop_level_maximum, | |||
drop_leagues = core.map.drop_leagues, | |||
drop_areas = core.map.drop_areas, | |||
drop_areas_html = core.map.drop_areas_html, | |||
drop_text = core.map.drop_text, | |||
required_level = core.map.required_level, | |||
required_level_final = core.map.required_level_final, | |||
required_dexterity = core.map.required_dexterity, | |||
required_strength = core.map.required_strength, | |||
required_intelligence = core.map.required_intelligence, | |||
inventory_icon = core.map.inventory_icon, | |||
alternate_art_inventory_icons = core.map.alternate_art_inventory_icons, | |||
buff_icon = core.map.buff_icon, | |||
cannot_be_traded_or_modified = core.map.cannot_be_traded_or_modified, | |||
help_text = core.map.help_text, | |||
flavour_text = core.map.flavour_text, | |||
tags = core.map.tags, | |||
metadata_id = core.map.metadata_id, | |||
is_corrupted = core.map.is_corrupted, | |||
is_relic = core.map.is_relic, | |||
quality = core.map.quality, | |||
base_item = core.map.base_item, | |||
base_item_id = core.map.base_item_id, | |||
base_item_page = core.map.base_item_page, | |||
frame_type = core.map.frame_type, | |||
mods = core.map.mods, | |||
explicit_mods = core.map.explicit_mods, | |||
implicit_mods = core.map.implicit_mods, | |||
name_list = core.map.name_list, | |||
description = core.map.description, | |||
}, | |||
} | |||
core.cargo.item_purchase_costs = { | |||
table = 'item_purchase_costs', | |||
fields = { | |||
amount = { | |||
field = 'amount', | |||
type = 'Integer', | |||
}, | |||
name = { | |||
field = 'name', | |||
type = 'String', | |||
}, | |||
rarity = { | |||
field = 'rarity', | |||
type = 'String', | |||
}, | |||
}, | |||
} | |||
core.cargo.item_stats = { | |||
table = 'item_stats', | |||
fields = { | |||
id = { | |||
field = 'id', | |||
type = 'String', | |||
}, | |||
min = { | |||
field = 'min', | |||
type = 'Integer', | |||
}, | |||
max = { | |||
field = 'max', | |||
type = 'Integer', | |||
}, | |||
avg = { | |||
field = 'avg', | |||
type = 'Integer', | |||
}, | |||
is_implicit = { | |||
field = 'is_implicit', | |||
type = 'Boolean', | |||
}, | |||
}, | |||
} | |||
-- There probably will be a table named "buffs" in the future, so "item_buffs" is the best solution here | |||
core.cargo.item_buffs = { | |||
table = 'item_buffs', | |||
fields = { | |||
id = core.map.buff_id, | |||
values = core.map.buff_values, | |||
stat_text = core.map.buff_stat_text, | |||
icon = core.map.buff_icon, | |||
}, | |||
} | |||
core.cargo.upgraded_from_sets = { | |||
table = 'upgraded_from_sets', | |||
fields = { | |||
set_id = { | |||
field = 'set_id', | |||
type = 'Integer', | |||
}, | |||
text = { | |||
field = 'text', | |||
type = 'Text', | |||
}, | |||
} | |||
} | |||
core.cargo.upgraded_from_groups = { | |||
table = 'upgraded_from_groups', | |||
fields = { | |||
group_id = { | |||
field = 'group_id', | |||
type = 'Integer', | |||
}, | |||
set_id = { | |||
field = 'set_id', | |||
type = 'Integer', | |||
}, | |||
item_id = { | |||
field = 'item_id', | |||
type = 'String', | |||
}, | |||
item_name = { | |||
field = 'item_name', | |||
type = 'String', | |||
}, | |||
item_page = { | |||
field = 'item_page', | |||
type = 'Page', | |||
}, | |||
integer = { | |||
field = 'amount', | |||
type = 'Integer', | |||
}, | |||
notes = { | |||
field = 'notes', | |||
type = 'Text', | |||
}, | |||
} | |||
} | |||
core.cargo.amulets = { | |||
table = 'amulets', | |||
fields = { | |||
is_talisman = core.map.is_talisman, | |||
talisman_tier = core.map.talisman_tier, | |||
}, | |||
} | |||
core.cargo.flasks = { | |||
table = 'flasks', | |||
fields = { | |||
-- All flasks | |||
duration = core.map.flask_duration, | |||
charges_max = core.map.charges_max, | |||
charges_per_use = core.map.charges_per_use, | |||
-- Life/Mana/Hybrid flasks | |||
life = core.map.flask_life, | |||
mana = core.map.flask_mana, | |||
}, | |||
} | |||
core.cargo.weapons = { | |||
table = 'weapons', | |||
fields = { | |||
critical_strike_chance = core.map.critical_strike_chance, | |||
attack_speed = core.map.attack_speed, | |||
range = core.map.range, | |||
physical_damage_min = core.map.physical_damage_min, | |||
physical_damage_max = core.map.physical_damage_max, | |||
physical_damage_html = core.map.physical_damage_html, | |||
fire_damage_html = core.map.fire_damage_html, | |||
cold_damage_html = core.map.cold_damage_html, | |||
lightning_damage_html = core.map.lightning_damage_html, | |||
chaos_damage_html = core.map.chaos_damage_html, | |||
damage_avg = core.map.damage_avg, | |||
damage_html = core.map.damage_html, | |||
-- Values added via stat population | |||
fire_damage_min = core.map.fire_damage_min, | |||
fire_damage_max = core.map.fire_damage_max, | |||
cold_damage_min = core.map.cold_damage_min, | |||
cold_damage_max = core.map.cold_damage_max, | |||
lightning_damage_min = core.map.lightning_damage_min, | |||
lightning_damage_max = core.map.lightning_damage_max, | |||
chaos_damage_min = core.map.chaos_damage_min, | |||
chaos_damage_max = core.map.chaos_damage_max, | |||
}, | |||
} | |||
core.cargo.armours = { | |||
table = 'armours', | |||
fields = { | |||
armour = core.map.armour, | |||
energy_shield = core.map.energy_shield, | |||
evasion = core.map.evasion, | |||
}, | |||
} | |||
core.cargo.shields = { | |||
table = 'shields', | |||
fields = { | |||
block = core.map.block, | |||
} | |||
} | |||
core.cargo.skill_gems = { | |||
table = 'skill_gems', | |||
fields = { | |||
gem_description = core.map.gem_description, | |||
dexterity_percent = core.map.dexterity_percent, | |||
strength_percent = core.map.strength_percent, | |||
intelligence_percent = core.map.intelligence_percent, | |||
primary_attribute = core.map.primary_attribute, | |||
gem_tags = core.map.gem_tags, | |||
skill_screenshot = core.map.skill_screenshot, | |||
-- Support Skill Gems | |||
support_gem_letter = core.map.support_gem_letter, | |||
support_gem_letter_html = core.map.support_gem_letter_html, | |||
}, | |||
} | |||
core.cargo.maps = { | |||
table = 'maps', | |||
fields = { | |||
tier = core.map.map_tier, | |||
guild_character = core.map.map_guild_character, | |||
area_id = core.map.map_area_id, | |||
unique_area_id = core.map.unique_map_area_id, | |||
-- REMOVE? | |||
area_level = core.map.map_area_level, | |||
unique_area_level = core.map.unique_map_area_level, | |||
}, | |||
} | |||
core.cargo.stackables = { | |||
table = 'stackables', | |||
fields = { | |||
stack_size = core.map.stack_size, | |||
stack_size_currency_tab = core.map.stack_size_currency_tab, | |||
cosmetic_type = core.map.cosmetic_type, | |||
}, | |||
} | |||
core.cargo.essences = { | |||
table = 'essences', | |||
fields = { | |||
level_restriction = core.map.essence_level_restriction, | |||
level = core.map.essence_level, | |||
}, | |||
} | |||
core.cargo.hideout_doodads = { | |||
table = 'hideout_doodads', | |||
fields = { | |||
is_master_doodad = core.map.is_master_doodad, | |||
master = core.map.master, | |||
master_level_requirement = core.map.master_level_requirement, | |||
master_favour_cost = core.map.master_favour_cost, | |||
variation_count = core.map.variation_count, | |||
}, | |||
} | |||
core.cargo.prophecies = { | |||
table = 'prophecies', | |||
fields = { | |||
prophecy_id = core.map.prophecy_id, | |||
prediction_text = core.map.prediction_text, | |||
seal_cost = core.map.seal_cost, | |||
objective = core.map.prophecy_objective, | |||
reward = core.map.prophecy_reward, | |||
}, | |||
} | |||
core.cargo.divination_cards = { | |||
table = 'divination_cards', | |||
fields = { | |||
card_art = core.map.card_art, | |||
}, | |||
} | |||
core.cargo.jewels = { | |||
table = 'jewels', | |||
fields = { | |||
item_limit = core.map.item_limit, | |||
radius_html = core.map.jewel_radius_html, | |||
}, | |||
} | |||
-- TODO: Second pass for i18n item classes | |||
-- base item is default, but will be validated later | |||
-- Notes: | |||
-- inventory_icon must always be before alternate_art_inventory_icons | |||
-- is_relic after rarity | |||
core.default_args = { | |||
'rarity', 'name', 'name_list', 'size_x', 'size_y', | |||
'drop_enabled', 'drop_level', 'drop_level_maximum', 'drop_leagues', 'drop_areas', 'drop_text', 'required_level', 'required_level_final', | |||
'inventory_icon', 'alternate_art_inventory_icons', 'flavour_text', | |||
'cannot_be_traded_or_modified', 'help_text', 'tags', 'metadata_id', 'is_corrupted', 'is_relic', 'purchase_costs', 'mods', 'implicit_mods', 'explicit_mods', | |||
'drop_areas_html', | |||
} | |||
-- frame_type is needed in stat_text | |||
core.late_args = {'frame_type', 'implicit_stat_text', 'explicit_stat_text', 'stat_text'} | |||
core.prophecy_args = {'prophecy_id', 'prediction_text', 'seal_cost', 'prophecy_objective', 'prophecy_reward'} | |||
core.tables = {'items'} | |||
core.class_groups = { | |||
flasks = { | |||
tables = {'flasks'}, | |||
keys = {['Life Flasks'] = true, ['Mana Flasks'] = true, ['Hybrid Flasks'] = true, ['Utility Flasks'] = true, ['Critical Utility Flasks'] = true}, | |||
args = {'quality', 'flask_duration', 'charges_max', 'charges_per_use'}, | |||
}, | |||
weapons = { | |||
tables = {'weapons'}, | |||
keys = {['Claws'] = true, ['Daggers'] = true, ['Wands'] = true, ['One Hand Swords'] = true, ['Thrusting One Hand Swords'] = true, ['One Hand Axes'] = true, ['One Hand Maces'] = true, ['Bows'] = true, ['Staves'] = true, ['Two Hand Swords'] = true, ['Two Hand Axes'] = true, ['Two Hand Maces'] = true, ['Sceptres'] = true, ['Fishing Rods'] = true}, | |||
args = {'quality', 'required_dexterity', 'required_intelligence', 'required_strength', 'critical_strike_chance', 'attack_speed', 'physical_damage_min', 'physical_damage_max', 'lightning_damage_min', 'lightning_damage_max', 'cold_damage_min', 'cold_damage_max', 'fire_damage_min', 'fire_damage_max', 'chaos_damage_min', 'chaos_damage_max', 'range'}, | |||
late_args = {'physical_damage_html', 'fire_damage_html', 'cold_damage_html', 'lightning_damage_html', 'chaos_damage_html', 'damage_avg', 'damage_html'}, | |||
}, | |||
gems = { | |||
tables = {'skill_gems'}, | |||
keys = {['Active Skill Gems'] = true, ['Support Skill Gems'] = true}, | |||
args = {'dexterity_percent', 'strength_percent', 'intelligence_percent', 'primary_attribute', 'gem_tags'}, | |||
}, | |||
armor = { | |||
tables = {'armours'}, | |||
keys = {['Gloves'] = true, ['Boots'] = true, ['Body Armours'] = true, ['Helmets'] = true, ['Shields'] = true}, | |||
args = {'quality', 'required_dexterity', 'required_intelligence', 'required_strength', 'armour', 'energy_shield', 'evasion'}, | |||
}, | |||
stackable = { | |||
tables = {'stackables'}, | |||
keys = {['Currency'] = true, ['Stackable Currency'] = true, ['Hideout Doodads'] = true, ['Microtransactions'] = true, ['Divination Card'] = true}, | |||
args = {'stack_size', 'stack_size_currency_tab', 'description', 'cosmetic_type'}, | |||
}, | |||
} | |||
core.class_specifics = { | |||
['Amulets'] = { | |||
args = {'is_talisman', 'talisman_tier'}, | |||
}, | |||
['Life Flasks'] = { | |||
args = {'flask_life'}, | |||
}, | |||
['Mana Flasks'] = { | |||
args = {'flask_mana'}, | |||
}, | |||
['Hybrid Flasks'] = { | |||
args = {'flask_life', 'flask_mana'}, | |||
}, | |||
['Utility Flasks'] = { | |||
tables = {'item_buffs'}, | |||
args = {'buff_id', 'buff_values', 'buff_stat_text', 'buff_icon'}, | |||
}, | |||
['Critical Utility Flasks'] = { | |||
tables = {'item_buffs'}, | |||
args = {'buff_id', 'buff_values', 'buff_stat_text', 'buff_icon'}, | |||
}, | |||
['Active Skill Gems'] = { | |||
args = {'skill_screenshot'}, | |||
defaults = { | |||
help_text = i18n.help_text_defaults.active_gem, | |||
size_x = 1, | |||
size_y = 1, | |||
}, | |||
frame_type = 'gem', | |||
}, | |||
['Support Skill Gems'] = { | |||
args = {'support_gem_letter', 'support_gem_letter_html'}, | |||
defaults = { | |||
help_text = i18n.help_text_defaults.support_gem, | |||
size_x = 1, | |||
size_y = 1, | |||
}, | |||
frame_type = 'gem', | |||
}, | |||
['Shields'] = { | |||
tables = {'shields'}, | |||
args = {'block'}, | |||
}, | |||
['Maps'] = { | |||
tables = {'maps'}, | |||
args = {'quality', 'map_tier', 'map_guild_character', 'map_area_id', 'map_area_level', 'unique_map_area_id', 'unique_map_area_level', 'unique_map_guild_character'}, | |||
skip_stat_lines = i18n.stat_skip_patterns.maps, | |||
}, | |||
['Currency'] = { | |||
frame_type = 'currency', | |||
}, | |||
['Stackable Currency'] = { | |||
args = {'is_essence', 'essence_level_restriction', 'essence_level'}, | |||
frame_type = 'currency', | |||
}, | |||
['Microtransactions'] = { | |||
frame_type = 'currency', | |||
}, | |||
['Hideout Doodads'] = { | |||
tables = {'hideout_doodads'}, | |||
args = {'is_master_doodad', 'master', 'master_level_requirement', 'master_favour_cost', 'variation_count'}, | |||
defaults = { | |||
help_text = i18n.help_text_defaults.hideout_doodad, | |||
}, | |||
frame_type = 'currency', | |||
}, | |||
['Jewel'] = { | |||
late_args = {'item_limit', 'jewel_radius_html'}, | |||
defaults = { | |||
help_text = i18n.help_text_defaults.jewel, | |||
}, | |||
skip_stat_lines = i18n.stat_skip_patterns.jewels, | |||
}, | |||
['Abyss Jewel'] = { | |||
late_args = {'item_limit', 'jewel_radius_html'}, | |||
skip_stat_lines = i18n.stat_skip_patterns.jewels, | |||
}, | |||
['Quest Items'] = { | |||
args = {'description'}, | |||
frame_type = 'quest', | |||
}, | |||
['Divination Card'] = { | |||
tables = {'divination_cards'}, | |||
args = {'card_art',}, | |||
frame_type = 'divicard', | |||
}, | |||
['Labyrinth Item'] = { | |||
frame_type = 'currency', | |||
}, | |||
['Labyrinth Trinket'] = { | |||
args = {tables_assoc, 'buff_icon'}, | |||
frame_type = 'currency', | |||
}, | |||
['Pantheon Soul'] = { | |||
defaults = { | |||
cannot_be_traded_or_modified = true, | |||
}, | |||
}, | |||
} | |||
-- add defaults from class specifics and class groups | |||
core.item_classes = {} | |||
core.item_classes_extend = {'tables', 'args', 'late_args'} | |||
function core.build_item_classes(tpl_args, frame) | |||
core.map.class.func(tpl_args, frame) | |||
-- Skip building for anything but the specified class. | |||
for _, data in ipairs(m_game.constants.item.class) do | |||
if data['full'] == tpl_args.class then | |||
core.item_classes[data['full']] = { | |||
tables = xtable:new(), | |||
args = xtable:new(), | |||
late_args = xtable:new(), | |||
defaults = {}, | |||
} | |||
core.item_classes[data['full']].tables:insertT(core.tables) | |||
break | |||
end | |||
end | |||
for _, row in pairs(core.class_groups) do | |||
for class, _ in pairs(row.keys) do | |||
if class == tpl_args.class then | |||
for _, k in ipairs(core.item_classes_extend) do | |||
if row[k] ~= nil then | |||
core.item_classes[class][k]:insertT(row[k]) | |||
end | |||
end | |||
break | |||
end | |||
end | |||
end | |||
local class_specifics = core.class_specifics[tpl_args.class] | |||
if class_specifics then | |||
for _, k in ipairs(core.item_classes_extend) do | |||
if class_specifics[k] ~= nil then | |||
core.item_classes[tpl_args.class][k]:insertT(class_specifics[k]) | |||
end | |||
end | |||
if class_specifics.defaults ~= nil then | |||
for key, value in pairs(class_specifics.defaults) do | |||
core.item_classes[tpl_args.class].defaults[key] = value | |||
end | |||
end | |||
end | |||
end | |||
-- GroupTable -> RowTable -> formatter function | |||
-- | |||
-- | |||
-- | |||
-- Contents here are meant to resemble the ingame infobox of items | |||
-- | |||
core.item_display_groups = { | |||
-- Tags, stats, level, etc | |||
{ | |||
{ | |||
args = {'cosmetic_type'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'cosmetic_type', | |||
fmt = '%s', | |||
color = 'default' | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = function(tpl_args, frame) | |||
if tpl_args.class == nil then | |||
return false | |||
end | |||
return core.class_groups.weapons.keys[tpl_args.class] ~= nil | |||
end, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'class', | |||
color = 'default', | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'gem_tags'}, | |||
func = function(tpl_args, frame) | |||
local out = {} | |||
for i, tag in ipairs(tpl_args.gem_tags) do | |||
out[#out+1] = string.format(i18n.gem_tag_category, tag, tag) | |||
end | |||
return table.concat(out, ', ') | |||
end, | |||
}, | |||
{ | |||
args = {'support_gem_letter_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'support_gem_letter_html', | |||
inline = i18n.tooltips.support_icon, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'radius'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'radius', | |||
inline = i18n.tooltips.radius, | |||
func = core.factory.descriptor_value{key='radius_description'}, | |||
}, | |||
[2] = { | |||
key = 'radius_secondary', | |||
inline = ' / %s', | |||
func = core.factory.descriptor_value{key='radius_secondary_description'}, | |||
}, | |||
[3] = { | |||
key = 'radius_tertiary', | |||
inline = ' / %s', | |||
func = core.factory.descriptor_value{key='radius_tertiary_description'}, | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- TODO: gem level here. Maybe put max level here? | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type='gem', | |||
options = { | |||
[1] = { | |||
key = 'mana_cost', | |||
hide_default = 100, | |||
fmt = function (tpl_args, frame) | |||
if tpl_args.has_percentage_mana_cost then | |||
return '%i%%' | |||
else | |||
return '%i' | |||
end | |||
end, | |||
inline = function (tpl_args, frame) | |||
if tpl_args.has_reservation_mana_cost then | |||
return i18n.tooltips.mana_reserved | |||
else | |||
return i18n.tooltips.mana_cost | |||
end | |||
end, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type='gem', | |||
options = { | |||
[1] = { | |||
key = 'mana_multiplier', | |||
hide_default = 100, | |||
fmt = '%i%%', | |||
inline = i18n.tooltips.mana_multiplier, | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- TODO: i18n | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type='gem', | |||
options = { | |||
[1] = { | |||
key = 'vaal_souls_requirement', | |||
hide_default = 0, | |||
fmt = '%i (N) / ', | |||
inline = i18n.tooltips.vaal_souls_per_use, | |||
}, | |||
[2] = { | |||
key = 'vaal_souls_requirement', | |||
hide_default = 0, | |||
fmt = '%i (C) / ', | |||
func = function (tpl_args, frame, value) | |||
return value*1.5 | |||
end, | |||
}, | |||
[3] = { | |||
key = 'vaal_souls_requirement', | |||
hide_default = 0, | |||
fmt = '%i (M)', | |||
func = function (tpl_args, frame, value) | |||
return value*2 | |||
end, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type='gem', | |||
options = { | |||
[1] = { | |||
key = 'vaal_stored_uses', | |||
hide_default = 0, | |||
fmt = '%i', | |||
inline = i18n.tooltips.stored_uses, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type='gem', | |||
options = { | |||
[1] = { | |||
key = 'stored_uses', | |||
hide_default = 0, | |||
fmt = '%i', | |||
inline = i18n.tooltips.stored_uses, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type='gem', | |||
options = { | |||
[1] = { | |||
key = 'cooldown', | |||
hide_default = 0, | |||
fmt = '%.2f sec', | |||
inline = i18n.tooltips.cooldown_time, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'cast_time'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'cast_time', | |||
hide_default = 0, | |||
fmt = '%.2f sec', | |||
inline = i18n.tooltips.cast_time, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type='gem', | |||
options = { | |||
[1] = { | |||
key = 'critical_strike_chance', | |||
hide_default = 0, | |||
fmt = '%.2f%%', | |||
inline = i18n.tooltips.critical_strike_chance, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type='gem', | |||
options = { | |||
[1] = { | |||
key = 'damage_effectiveness', | |||
hide_default = 100, | |||
fmt = '%i%%', | |||
inline = i18n.tooltips.damage_effectiveness, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'projectile_speed'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'projectile_speed', | |||
inline = i18n.tooltips.projectile_speed, | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- Quality is before item stats, but after gem stuff and item class | |||
{ | |||
args = {'quality'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'quality', | |||
fmt = '+%i%%', | |||
color = 'mod', | |||
inline = i18n.tooltips.quality, | |||
hide_default = 0, | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- Weapon only | |||
{ | |||
args = {'physical_damage_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'physical_damage_html', | |||
fmt = '%s', | |||
inline = i18n.tooltips.physical_damage, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = nil, | |||
func = function(tpl_args, frame) | |||
local text = '' | |||
for _, dtype in ipairs({'fire_damage_html', 'cold_damage_html', 'lightning_damage_html'}) do | |||
local value = tpl_args[dtype] | |||
if value ~= nil then | |||
text = text .. ' ' .. value | |||
end | |||
end | |||
if text ~= '' then | |||
return string.format(i18n.tooltips.elemental_damage, text) | |||
else | |||
return | |||
end | |||
end, | |||
}, | |||
{ | |||
args = {'chaos_damage_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'chaos_damage_html', | |||
fmt = '%s', | |||
inline = i18n.tooltips.chaos_damage, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'critical_strike_chance_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'critical_strike_chance_html', | |||
fmt = '%s', | |||
inline = i18n.tooltips.critical_strike_chance, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'attack_speed_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'attack_speed_html', | |||
fmt = '%s', | |||
inline = i18n.tooltips.attacks_per_second, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'range_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'range_html', | |||
fmt = '%s', | |||
inline = i18n.tooltips.weapon_range, | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- Map only | |||
{ | |||
args = {'map_area_level'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'map_area_level', | |||
fmt = '%i', | |||
inline = i18n.tooltips.map_level, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'map_tier'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'map_tier', | |||
fmt = '%i', | |||
inline = i18n.tooltips.map_tier, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = function(tpl_args, frame) | |||
return tpl_args.map_guild_character ~= nil and tpl_args.rarity == 'Normal' | |||
end, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'map_guild_character', | |||
fmt = '%s', | |||
inline = i18n.tooltips.map_guild_character, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = function(tpl_args, frame) | |||
return tpl_args.unique_map_guild_character ~= nil and tpl_args.rarity == 'Unique' | |||
end, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'unique_map_guild_character', | |||
fmt = '%s', | |||
inline = i18n.tooltips.map_guild_character, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type = 'stat', | |||
options = { | |||
[1] = { | |||
key = 'map_item_drop_quantity_+%', | |||
fmt = '+%i%%', | |||
color = 'mod', | |||
inline = i18n.tooltips.item_quantity, | |||
hide_default = 0, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type = 'stat', | |||
options = { | |||
[1] = { | |||
key = 'map_item_drop_rarity_+%', | |||
fmt = '+%i%%', | |||
color = 'mod', | |||
inline = i18n.tooltips.item_rarity, | |||
hide_default = 0, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
type = 'stat', | |||
options = { | |||
[1] = { | |||
key = 'map_pack_size_+%', | |||
fmt = '+%i%%', | |||
color = 'mod', | |||
inline = i18n.tooltips.monster_pack_size, | |||
hide_default = 0, | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- Jewel Only | |||
{ | |||
args = nil, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'item_limit', | |||
fmt = '%i', | |||
inline = i18n.tooltips.limited_to, | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'jewel_radius_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'jewel_radius_html', | |||
fmt = '%s', | |||
inline = i18n.tooltips.radius, | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- Flask only | |||
{ | |||
args = {'flask_mana_html', 'flask_duration_html'}, | |||
--func = core.factory.display_flask('flask_mana'), | |||
func = core.factory.display_value{ | |||
inline = i18n.tooltips.flask_mana_recovery, | |||
options = { | |||
[1] = { | |||
key = 'flask_mana_html', | |||
fmt = '%s', | |||
}, | |||
[2] = { | |||
key = 'flask_duration_html', | |||
fmt = '%s', | |||
}, | |||
} | |||
}, | |||
}, | |||
{ | |||
args = {'flask_life_html', 'flask_duration_html'}, | |||
func = core.factory.display_value{ | |||
inline = i18n.tooltips.flask_life_recovery, | |||
options = { | |||
[1] = { | |||
key = 'flask_life_html', | |||
fmt = '%s', | |||
}, | |||
[2] = { | |||
key = 'flask_duration_html', | |||
fmt = '%s', | |||
}, | |||
} | |||
}, | |||
}, | |||
{ | |||
-- don't display for mana/life flasks | |||
args = function(tpl_args, frame) | |||
for _, k in ipairs({'flask_life_html', 'flask_mana_html'}) do | |||
if tpl_args[k] ~= nil then | |||
return false | |||
end | |||
end | |||
return tpl_args['flask_duration_html'] ~= nil | |||
end, | |||
func = core.factory.display_value{ | |||
inline = i18n.tooltips.flask_duration, | |||
options = { | |||
[1] = { | |||
key = 'flask_duration_html', | |||
fmt = '%s', | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'charges_per_use_html', 'charges_max_html'}, | |||
func = core.factory.display_value{ | |||
inline = i18n.tooltips.flask_charges_per_use, | |||
options = { | |||
[1] = { | |||
key = 'charges_per_use_html', | |||
fmt = '%s', | |||
}, | |||
[2] = { | |||
key = 'charges_max_html', | |||
fmt = '%s', | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'buff_stat_text'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'buff_stat_text', | |||
color = 'mod', | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- armor | |||
{ | |||
args = {'block_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'block_html', | |||
inline = i18n.tooltips.chance_to_block, | |||
fmt = '%s', | |||
hide_default = 0, | |||
hide_default_key = 'block', | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'armour_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'armour_html', | |||
inline = i18n.tooltips.armour, | |||
fmt = '%s', | |||
hide_default = 0, | |||
hide_default_key = 'armour', | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'evasion_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'evasion_html', | |||
inline = i18n.tooltips.evasion, | |||
fmt = '%s', | |||
hide_default = 0, | |||
hide_default_key = 'evasion', | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'energy_shield_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'energy_shield_html', | |||
inline = i18n.tooltips.energy_shield, | |||
fmt = '%s', | |||
hide_default = 0, | |||
hide_default_key = 'energy_shield', | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- Amulet only | |||
{ | |||
args = {'talisman_tier'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'talisman_tier', | |||
fmt = '%i', | |||
inline = i18n.tooltips.talisman_tier, | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- Misc | |||
{ | |||
args = {'stack_size'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'stack_size', | |||
hide_default = 1, | |||
fmt = '%i', | |||
inline = i18n.tooltips.stack_size, | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- Essence stuff | |||
{ | |||
args = {'essence_level'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'essence_level', | |||
fmt = '%i', | |||
inline = i18n.tooltips.essence_level, | |||
}, | |||
}, | |||
}, | |||
}, | |||
}, | |||
-- Requirements | |||
{ | |||
-- TODO: i18n Master name? | |||
{ | |||
args = {'master', 'master_level_requirement'}, | |||
func = function(tpl_args, frame) | |||
-- masters have been validated before | |||
local data | |||
for i, rowdata in ipairs(m_game.constants.masters) do | |||
if tpl_args.master == rowdata.full then | |||
data = rowdata | |||
break | |||
end | |||
end | |||
return m_util.html.poe_color('default', i18n.tooltips.requires, string.format('[[%s|%s %s]]', data.full, data.short_upper, tpl_args.master_level_requirement)) | |||
end | |||
}, | |||
-- Instead of item level, show drop level if any | |||
{ | |||
args = nil, | |||
func = function(tpl_args, frame) | |||
local opt = { | |||
[1] = { | |||
key = 'required_level_final_html', | |||
hide_default = 1, | |||
hide_default_key = 'required_level_final', | |||
inline = i18n.tooltips.level_inline, | |||
inline_color = false, | |||
}, | |||
} | |||
for _, attr in ipairs(m_game.constants.attributes) do | |||
opt[#opt+1] = { | |||
key = string.format('required_%s_html', attr['long_lower']), | |||
hide_default = 0, | |||
hide_default_key = string.format('required_%s', attr['long_lower']), | |||
inline = ', %s ' .. attr['short_upper'], | |||
inline_color = false, | |||
} | |||
end | |||
local requirements = core.factory.display_value{options = opt}(tpl_args, frame) | |||
-- return early | |||
if requirements == nil then | |||
return | |||
end | |||
requirements = string.gsub(requirements, '^, ', '') | |||
return m_util.html.poe_color('default', string.format(i18n.tooltips.requires, requirements)) | |||
end, | |||
}, | |||
}, | |||
-- Gem description | |||
{ | |||
css_class = '-textwrap tc -gemdesc', | |||
{ | |||
args = {'gem_description'}, | |||
func = core.factory.display_value_only('gem_description'), | |||
}, | |||
}, | |||
-- Gem Quality Stats | |||
{ | |||
css_class = '-textwrap tc -mod', | |||
{ | |||
args = {'quality_stat_text'}, | |||
func = function(tpl_args, frame) | |||
lines = {} | |||
lines[#lines+1] = m_util.html.poe_color('default', i18n.tooltips.gem_quality) | |||
lines[#lines+1] = tpl_args.quality_stat_text | |||
return table.concat(lines, '<br>') | |||
end, | |||
}, | |||
}, | |||
-- Gem Implicit Stats | |||
{ | |||
css_class = '-textwrap tc -mod', | |||
{ | |||
args = function(tpl_args, frame) | |||
return core.class_groups.gems.keys[tpl_args.class] and tpl_args.stat_text | |||
end, | |||
func = function(tpl_args, frame) | |||
lines = {} | |||
lines[#lines+1] = tpl_args.stat_text | |||
if tpl_args.gem_tags:contains('Vaal') then | |||
lines[#lines+1] = m_util.html.poe_color('corrupted', i18n.tooltips.corrupted) | |||
end | |||
return table.concat(lines, '<br>') | |||
end, | |||
}, | |||
}, | |||
-- Implicit Stats | |||
{ | |||
css_class = '-textwrap tc -mod', | |||
func = function(tpl_args, frame) | |||
if tpl_args.implicit_stat_text ~= '' then | |||
return {tpl_args.implicit_stat_text} | |||
else | |||
return {} | |||
end | |||
end, | |||
}, | |||
-- Stats | |||
{ | |||
css_class = '-textwrap tc -mod', | |||
func = function(tpl_args, frame) | |||
if tpl_args.explicit_stat_text ~= '' then | |||
return {tpl_args.explicit_stat_text} | |||
else | |||
return {} | |||
end | |||
end, | |||
}, | |||
-- Experience | |||
--[[{ | |||
{ | |||
args = {'experience'}, | |||
func = core.factory.display_value{ | |||
key = 'experience', | |||
options = { | |||
[1] = { | |||
fmt = '%i', | |||
}, | |||
}, | |||
}, | |||
}, | |||
},]]-- | |||
-- Description (currency, doodads) | |||
{ | |||
css_class = '-textwrap tc -mod', | |||
{ | |||
args = {'description'}, | |||
func = core.factory.display_value_only('description'), | |||
}, | |||
}, | |||
-- Variations (for doodads) | |||
{ | |||
css_class = 'tc -mod', | |||
{ | |||
args = {'variation_count'}, | |||
func = function(tpl_args, frame) | |||
local txt | |||
if tpl_args.variation_count == 1 then | |||
txt = i18n.tooltips.variation_singular | |||
else | |||
txt = i18n.tooltips.variation_plural | |||
end | |||
return string.format('%i %s', tpl_args.variation_count, txt) | |||
end, | |||
}, | |||
}, | |||
-- Flavour Text | |||
{ | |||
css_class = '-textwrap tc -flavour', | |||
{ | |||
args = {'flavour_text'}, | |||
func = core.factory.display_value_only('flavour_text'), | |||
}, | |||
}, | |||
-- Prophecy text | |||
{ | |||
css_class = '-textwrap tc -value', | |||
{ | |||
args = {'prediction_text'}, | |||
func = core.factory.display_value_only('prediction_text'), | |||
}, | |||
}, | |||
-- Can not be traded or modified | |||
{ | |||
css_class = '-textwrap tc -canttradeormodify', | |||
{ | |||
args = {'cannot_be_traded_or_modified'}, | |||
func = function(tpl_args, frame) | |||
if tpl_args.cannot_be_traded_or_modified == true then | |||
return i18n.tooltips.cannot_be_traded_or_modified | |||
end | |||
end, | |||
}, | |||
}, | |||
-- Help text | |||
{ | |||
css_class = '-textwrap tc -help', | |||
{ | |||
args = {'help_text'}, | |||
func = core.factory.display_value_only('help_text'), | |||
}, | |||
}, | |||
-- Cost (i.e. vendor costs) | |||
{ | |||
--css_class = '', | |||
{ | |||
args = {'master_favour_cost'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'master_favour_cost', | |||
inline = i18n.tooltips.favour_cost, | |||
color = 'currency', | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'seal_cost'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'seal_cost', | |||
fmt = '%dx ', | |||
color = 'currency', | |||
inline = function (tpl_args, frame) | |||
return i18n.tooltips.seal_cost .. f_item_link{item_name_exact='Silver Coin', html=''} | |||
end, | |||
}, | |||
}, | |||
}, | |||
}, | |||
}, | |||
} | |||
-- | -- | ||
-- | -- This is meant to show additional information about the item in a separate infobox | ||
-- | -- | ||
core.extra_display_groups = { | |||
-- Drop info | |||
{ | |||
header = i18n.tooltips.drop_restrictions, | |||
{ | |||
args = {'drop_enabled'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = 'drop_level', | |||
fmt = '%i', | |||
inline = i18n.tooltips.level, | |||
}, | |||
[2] = { | |||
key = 'drop_level_maximum', | |||
hide_default = 100, | |||
fmt = '%i', | |||
inline = ' / %s', | |||
}, | |||
}, | |||
}, | |||
}, | |||
{ | |||
args = {'drop_leagues'}, | |||
func = function(tpl_args, frame) | |||
return string.format(i18n.tooltips.league_restriction, m_util.html.poe_color('value', table.concat(tpl_args.drop_leagues, ', '))) | |||
end | |||
}, | |||
{ | |||
args = {'drop_areas_html'}, | |||
func = core.factory.display_value_only('drop_areas_html'), | |||
}, | |||
{ | |||
args = {'drop_text'}, | |||
func = core.factory.display_value_only('drop_text'), | |||
}, | |||
{ | |||
args = function(tpl_args, frame) | |||
if tpl_args.drop_enabled == true then | |||
return false | |||
end | |||
return true | |||
end, | |||
func = function(tpl_args, frame) | |||
local span = mw.html.create('span') | |||
span | |||
:attr('class', 'infobox-disabled-drop') | |||
:wikitext(i18n.tooltips.drop_disabled) | |||
:done() | |||
return tostring(span) | |||
end, | |||
}, | |||
}, | |||
{ | |||
header = i18n.tooltips.purchase_costs, | |||
{ | |||
args = function(tpl_args, frame) | |||
for rarity, data in pairs(tpl_args.purchase_costs) do | |||
if #data > 0 then | |||
return true | |||
end | |||
end | |||
return false | |||
end, | |||
func = function(tpl_args, frame) | |||
local tbl = mw.html.create('table') | |||
tbl | |||
--:attr('class', 'wikitable') | |||
:attr('style', 'width: 100%; margin-top: 0px;') | |||
for _, rarity_names in ipairs(m_game.constants.item.rarity) do | |||
local data = tpl_args.purchase_costs[rarity_names.long_lower] | |||
if #data > 0 then | |||
local tr = tbl:tag('tr') | |||
tr | |||
:tag('td') | |||
:wikitext(rarity_names.long_upper) | |||
local td = tr:tag('td') | |||
for _, purchase_data in ipairs(data) do | |||
td:wikitext(string.format('%dx [[%s]]<br />', purchase_data.amount, purchase_data.name)) | |||
end | |||
end | |||
end | |||
return tostring(tbl) | |||
end, | |||
}, | |||
}, | |||
{ | |||
header = i18n.tooltips.sell_price, | |||
{ | |||
args = {'sell_price_order'}, | |||
func = function(tpl_args, frame) | |||
local out = {} | |||
for _, item_name in ipairs(tpl_args.sell_price_order) do | |||
out[#out+1] = string.format('%dx [[%s]]', tpl_args.sell_prices[item_name], item_name) | |||
end | |||
return table.concat(out, '<br />') | |||
end, | |||
}, | |||
}, | |||
-- Damage per second | |||
{ | |||
header = i18n.tooltips.damage_per_second, | |||
-- Autoinsert here from dps map | |||
}, | |||
} | |||
local | for i, data in ipairs(core.dps_map) do | ||
table.insert(core.extra_display_groups[4], { | |||
args = {data.name .. '_html'}, | |||
func = core.factory.display_value{ | |||
options = { | |||
[1] = { | |||
key = data.name .. '_html', | |||
inline = data.label_infobox .. ': %s', | |||
fmt = '%s', | |||
-- the html already contains the colour | |||
no_color = true, | |||
}, | |||
}, | |||
}, | |||
}) | |||
if i == 5 then | |||
table.insert(core.extra_display_groups[4], { | |||
args = function (tpl_args, frame) | |||
return tpl_args.elemental_dps_html ~= nil or tpl_args.poison_dps_html ~= nil | |||
end, | |||
func = function (tpl_args, frame) | |||
return '' | |||
end, | |||
}) | |||
elseif i == 7 then | |||
table.insert(core.extra_display_groups[4], { | |||
args = {'dps_html'}, | |||
func = function (tpl_args, frame) | |||
return '' | |||
end, | |||
}) | |||
end | |||
end | |||
core.result = {} | |||
-- for sort type see: | |||
-- https://meta.wikimedia.org/wiki/Help:Sorting | |||
core.result.generic_item = { | |||
{ | |||
arg = 'base_item', | |||
header = i18n.item_table.base_item, | |||
fields = {'items.base_item', 'items.base_item_page'}, | |||
display = function(tr, data) | |||
tr | |||
:tag('td') | |||
:attr('data-sort-value', data['items.base_item']) | |||
:wikitext(string.format('[[%s|%s]]', data['items.base_item_page'], data['items.base_item'])) | |||
end, | |||
order = 1000, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'class', | |||
header = i18n.item_table.item_class, | |||
fields = {'items.class'}, | |||
display = h.tbl.display.factory.value{options = { | |||
[1] = { | |||
fmt='[[%s]]', | |||
}, | |||
}}, | |||
order = 1001, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'essence', | |||
header = i18n.item_table.essence_level, | |||
fields = {'essences.level'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 2000, | |||
}, | |||
{ | |||
arg = {'drop', 'drop_level'}, | |||
header = i18n.item_table.drop_level, | |||
fields = {'items.drop_level'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 3000, | |||
}, | |||
{ | |||
arg = 'stack_size', | |||
header = i18n.item_table.stack_size, | |||
fields = {'stackables.stack_size'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 4000, | |||
}, | |||
{ | |||
arg = 'stack_size_currency_tab', | |||
header = i18n.item_table.stack_size_currency_tab, | |||
fields = {'stackables.stack_size_currency_tab'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 4001, | |||
}, | |||
{ | |||
arg = 'level', | |||
header = m_game.level_requirement.icon, | |||
fields = h.tbl.range_fields('items.required_level'), | |||
display = h.tbl.display.factory.range{field='items.required_level'}, | |||
order = 5000, | |||
}, | |||
{ | |||
arg = 'ar', | |||
header = i18n.item_table.armour, | |||
fields = h.tbl.range_fields('armours.armour'), | |||
display = h.tbl.display.factory.range{field='armours.armour'}, | |||
order = 6000, | |||
}, | |||
{ | |||
arg = 'ev', | |||
header =i18n.item_table.evasion, | |||
fields = h.tbl.range_fields('armours.evasion'), | |||
display = h.tbl.display.factory.range{field='armours.evasion'}, | |||
order = 6001, | |||
}, | |||
{ | |||
arg = 'es', | |||
header = i18n.item_table.energy_shield, | |||
fields = h.tbl.range_fields('armours.energy_shield'), | |||
display = h.tbl.display.factory.range{field='armours.energy_shield'}, | |||
order = 6002, | |||
}, | |||
{ | |||
arg = 'block', | |||
header = i18n.item_table.block, | |||
fields = h.tbl.range_fields('shields.block'), | |||
display = h.tbl.display.factory.range{field='shields.block'}, | |||
order = 6003, | |||
}, | |||
--[[{ | |||
arg = 'physical_damage_min', | |||
header = m_util.html.abbr('Min', 'Local minimum weapon damage'), | |||
fields = h.tbl.range_fields('minimum physical damage'), | |||
display = h.tbl.display.factory.range{field='minimum physical damage'}, | |||
order = 7000, | |||
}, | |||
{ | |||
arg = 'physical_damage_max', | |||
header = m_util.html.abbr('Max', 'Local maximum weapon damage'), | |||
fields = h.tbl.range_fields('maximum physical damage'), | |||
display = h.tbl.display.factory.range{field='maximum physical damage'}, | |||
order = 7001, | |||
},]]-- | |||
{ | |||
arg = {'weapon', 'damage'}, | |||
header = i18n.item_table.damage, | |||
fields = {'weapons.damage_html', 'weapons.damage_avg'}, | |||
display = function (tr, data) | |||
tr | |||
:tag('td') | |||
:attr('data-sort-value', data['weapons.damage_avg']) | |||
:wikitext(data['weapons.damage_html']) | |||
end, | |||
order = 8000, | |||
}, | |||
{ | |||
arg = {'weapon', 'aps'}, | |||
header = i18n.item_table.attacks_per_second, | |||
fields = h.tbl.range_fields('weapons.attack_speed'), | |||
display = h.tbl.display.factory.range{field='weapons.attack_speed'}, | |||
order = 8001, | |||
}, | |||
{ | |||
arg = {'weapon', 'crit'}, | |||
header = i18n.item_table.local_critical_strike_chance, | |||
fields = h.tbl.range_fields('weapons.critical_strike_chance'), | |||
display = h.tbl.display.factory.range{field='weapons.critical_strike_chance'}, | |||
order = 8002, | |||
}, | |||
{ | |||
arg = 'flask_life', | |||
header = i18n.item_table.flask_life, | |||
fields = h.tbl.range_fields('flasks.life'), | |||
display = h.tbl.display.factory.range{field='flasks.life'}, | |||
order = 9000, | |||
}, | |||
{ | |||
arg = 'flask_mana', | |||
header = i18n.item_table.flask_mana, | |||
fields = h.tbl.range_fields('flasks.mana'), | |||
display = h.tbl.display.factory.range{field='flasks.mana'}, | |||
order = 9001, | |||
}, | |||
{ | |||
arg = 'flask', | |||
header = i18n.item_table.flask_duration, | |||
fields = h.tbl.range_fields('flasks.duration'), | |||
display = h.tbl.display.factory.range{field='flasks.duration'}, | |||
order = 9002, | |||
}, | |||
{ | |||
arg = 'flask', | |||
header = i18n.item_table.flask_charges_per_use, | |||
fields = h.tbl.range_fields('flasks.charges_per_use'), | |||
display = h.tbl.display.factory.range{field='flasks.charges_per_use'}, | |||
order = 9003, | |||
}, | |||
{ | |||
arg = 'flask', | |||
header = i18n.item_table.flask_maximum_charges, | |||
fields = h.tbl.range_fields('flasks.charges_max'), | |||
display = h.tbl.display.factory.range{field='flasks.charges_max'}, | |||
order = 9004, | |||
}, | |||
{ | |||
arg = 'item_limit', | |||
header = i18n.item_table.item_limit, | |||
fields = {'jewels.item_limit'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 10000, | |||
}, | |||
{ | |||
arg = 'jewel_radius', | |||
header = i18n.item_table.jewel_radius, | |||
fields = {'jewels.radius_html'}, | |||
display = function (tr, data) | |||
tr | |||
:tag('td') | |||
:wikitext(data['jewels.radius_html']) | |||
end, | |||
order = 10001, | |||
}, | |||
{ | |||
arg = 'map_tier', | |||
header = i18n.item_table.map_tier, | |||
fields = {'maps.tier'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 11000, | |||
}, | |||
{ | |||
arg = 'map_level', | |||
header = i18n.item_table.map_level, | |||
fields = {'maps.area_level'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 11010, | |||
}, | |||
{ | |||
arg = 'map_guild_character', | |||
header = i18n.item_table.map_guild_character, | |||
fields = {'maps.guild_character'}, | |||
display = h.tbl.display.factory.value{colour='value'}, | |||
order = 11020, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'buff', | |||
header = i18n.item_table.buff_effects, | |||
fields = {'item_buffs.stat_text'}, | |||
display = h.tbl.display.factory.value{colour='mod'}, | |||
order = 12000, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'stat', | |||
header = i18n.item_table.stats, | |||
fields = {'items.stat_text'}, | |||
display = h.tbl.display.factory.value{colour='mod'}, | |||
order = 12001, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'description', | |||
header = i18n.item_table.effects, | |||
fields = {'items.description'}, | |||
display = h.tbl.display.factory.value{colour='mod'}, | |||
order = 12002, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'flavour_text', | |||
header = i18n.item_table.flavour_text, | |||
fields = {'items.flavour_text'}, | |||
display = h.tbl.display.factory.value{colour='flavour'}, | |||
order = 12003, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'help_text', | |||
header = i18n.item_table.help_text, | |||
fields = {'items.help_text'}, | |||
display = h.tbl.display.factory.value{colour='help'}, | |||
order = 12005, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = {'prophecy', 'objective'}, | |||
header = i18n.item_table.objective, | |||
fields = {'prophecies.objective'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 13002, | |||
}, | |||
{ | |||
arg = {'prophecy', 'reward'}, | |||
header = i18n.item_table.reward, | |||
fields = {'prophecies.reward'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 13001, | |||
}, | |||
{ | |||
arg = {'prophecy', 'seal_cost'}, | |||
header = i18n.item_table.seal_cost, | |||
fields = {'prophecies.seal_cost'}, | |||
display = h.tbl.display.factory.value{colour='currency'}, | |||
order = 13002, | |||
}, | |||
{ | |||
arg = {'prediction_text'}, | |||
header = i18n.item_table.prediction_text, | |||
fields = {'prophecies.prediction_text'}, | |||
display = h.tbl.display.factory.value{colour='value'}, | |||
order = 12004, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'buff_icon', | |||
header = i18n.item_table.buff_icon, | |||
fields = {'item_buffs.icon'}, | |||
display = h.tbl.display.factory.value{options = { | |||
[1] = { | |||
fmt='[[%s]]', | |||
}, | |||
}}, | |||
order = 14000, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = {'drop', 'drop_leagues'}, | |||
header = i18n.item_table.drop_leagues, | |||
fields = {'items.drop_leagues'}, | |||
display = function (tr, data) | |||
tr | |||
:tag('td') | |||
:wikitext(table.concat(m_util.string.split(data['items.drop_leagues'], ','), '<br>')) | |||
end, | |||
order = 15000, | |||
}, | |||
{ | |||
arg = {'drop', 'drop_areas'}, | |||
header = i18n.item_table.drop_areas, | |||
fields = {'items.drop_areas_html'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 15001, | |||
}, | |||
{ | |||
arg = {'drop', 'drop_text'}, | |||
header = i18n.item_table.drop_text, | |||
fields = {'items.drop_text'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 15002, | |||
}, | |||
} | |||
for i, data in ipairs(core.dps_map) do | |||
table.insert(core.result.generic_item, { | |||
arg = data.name, | |||
header = data.label, | |||
fields = h.tbl.range_fields(string.format('weapons.%s', data.field)), | |||
display = h.tbl.display.factory.range{field=string.format('weapons.%s', data.field)}, | |||
order = 8100+i, | |||
}) | |||
end | |||
core.result.skill_gem_new = { | |||
{ | |||
arg = 'icon', | |||
header = i18n.item_table.support_gem_letter, | |||
fields = {'skill_gems.support_gem_letter_html'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 1000, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'skill_icon', | |||
header = i18n.item_table.skill_icon, | |||
fields = {'skills3.skill_icon'}, | |||
display = h.tbl.display.factory.value{options = { | |||
[1] = { | |||
fmt='[[%s]]', | |||
}, | |||
}}, | |||
order = 1001, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'description', | |||
header = i18n.item_table.description, | |||
fields = {'skills3.description'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 2000, | |||
sort_type = 'text', | |||
}, | |||
{ | |||
arg = 'level', | |||
header = m_game.level_requirement.icon, | |||
fields = {'items.level_requirement'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 3004, | |||
}, | |||
{ | |||
arg = 'crit', | |||
header = i18n.item_table.skill_critical_strike_chance, | |||
fields = {'skill_levels.critical_strike_chance'}, | |||
display = h.tbl.display.factory.value{options = { | |||
[1] = { | |||
fmt='%s%%', | |||
}, | |||
}}, | |||
order = 4000, | |||
}, | |||
{ | |||
arg = 'cast_time', | |||
header = i18n.item_table.cast_time, | |||
fields = {'skill_levels.cast_time'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 4001, | |||
}, | |||
{ | |||
arg = 'dmgeff', | |||
header = i18n.item_table.damage_effectiveness, | |||
fields = {'skill_levels.damage_effectiveness'}, | |||
display = h.tbl.display.factory.value{options = { | |||
[1] = { | |||
fmt='%s%%', | |||
}, | |||
}}, | |||
order = 4002, | |||
}, | |||
{ | |||
arg = 'mcm', | |||
header = i18n.item_table.mana_cost_multiplier, | |||
fields = {'skill_levels.mana_multiplier'}, | |||
display = h.tbl.display.factory.value{options = { | |||
[1] = { | |||
fmt='%s%%', | |||
}, | |||
}}, | |||
order = 5000, | |||
}, | |||
{ | |||
arg = 'mana', | |||
header = i18n.item_table.mana_cost, | |||
fields = {'skill_levels.mana_cost', 'skills3.has_percentage_mana_cost', 'skills3.has_reservation_mana_cost'}, | |||
display = function (tr, data) | |||
local appendix = '' | |||
if m_util.cast.boolean(data['skills3.has_percentage_mana_cost']) then | |||
appendix = appendix .. '%' | |||
end | |||
if m_util.cast.boolean(data['skills3.has_reservation_mana_cost']) then | |||
appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix | |||
end | |||
tr | |||
:tag('td') | |||
:attr('data-sort-value', data['skill_levels.mana_cost']) | |||
:wikitext(string.format('%d', data['skill_levels.mana_cost']) .. appendix) | |||
end, | |||
order = 5001, | |||
}, | |||
{ | |||
arg = 'vaal', | |||
header = i18n.item_table.vaal_souls_requirement, | |||
fields = {'skill_levels.vaal_souls_requirement'}, | |||
display = function (tr, data) | |||
local souls = tonumber(data['skill_levels.vaal_souls_requirement']) | |||
tr | |||
:tag('td') | |||
:attr('data-sort-value', souls) | |||
:wikitext(string.format('%d / %d / %d', souls, souls*1.5, souls*2)) | |||
end, | |||
order = 6000, | |||
}, | |||
{ | |||
arg = 'vaal', | |||
header = i18n.item_table.stored_uses, | |||
fields = {'skill_levels.vaal_stored_uses'}, | |||
display = h.tbl.display.factory.value{}, | |||
order = 6001, | |||
}, | |||
{ | |||
arg = 'radius', | |||
header = i18n.item_table.primary_radius, | |||
fields = {'skills3.radius', 'skills3.radius_description'}, | |||
options = {[2] = {optional = true}}, | |||
display = function (tr, data) | |||
tr | |||
:tag('td') | |||
:attr('data-sort-value', data['skills3.radius']) | |||
:wikitext(core.factory.descriptor_value{tbl=data, key='skills3.radius_description'}(nil, nil, data['skills3.radius'])) | |||
end, | |||
order = 7000, | |||
}, | |||
{ | |||
arg = 'radius', | |||
header = i18n.item_table.secondary_radius, | |||
fields = {'skills3.radius_secondary', 'skills3.radius_secondary_description'}, | |||
options = {[2] = {optional = true}}, | |||
display = function (tr, data) | |||
tr | |||
:tag('td') | |||
:attr('data-sort-value', data['skills3.radius_secondary']) | |||
:wikitext(core.factory.descriptor_value{tbl=data, key='skills3.radius_secondary_description'}(nil, nil, data['skills3.radius_secondary'])) | |||
end, | |||
order = 7001, | |||
}, | |||
{ | |||
arg = 'radius', | |||
header = i18n.item_table.tertiary_radius, | |||
fields = {'skills3.radius_tertiary', 'skills3.radius_tertiary_description'}, | |||
options = {[2] = {optional = true}}, | |||
display = function (tr, data) | |||
tr | |||
:tag('td') | |||
:attr('data-sort-value', data['skills3.radius_tertiary']) | |||
:wikitext(core.factory.descriptor_value{tbl=data, key='skills3.radius_tertiary_description'}(nil, nil, data['skills3.radius_tertiary'])) | |||
end, | |||
order = 7002, | |||
}, | |||
} | |||
for i, attr in ipairs(m_game.constants.attributes) do | |||
table.insert(core.result.generic_item, 7, { | |||
arg = attr.short_lower, | |||
header = attr.icon, | |||
fields = h.tbl.range_fields(string.format('items.required_%s', attr.long_lower)), | |||
display = h.tbl.display.factory.range{field=string.format('items.required_%s', attr.long_lower)}, | |||
order = 5000+i, | |||
}) | |||
table.insert(core.result.skill_gem_new, 1, { | |||
arg = attr.short_lower, | |||
header = attr.icon, | |||
fields = {string.format('skill_gems.%s_percent', attr.long_lower)}, | |||
display = function (tr, data) | |||
tr | |||
:tag('td') | |||
:attr('data-sort-value', data[string.format('skill_gems.%s_percent', attr.long_lower)]) | |||
:wikitext('[[File:Yes.png|yes|link=]]') | |||
end, | |||
order = 3000+i, | |||
}) | |||
end | |||
-- ---------------------------------------------------------------------------- | |||
-- Tables | |||
-- ---------------------------------------------------------------------------- | |||
function cargo_declare(data) | |||
return function(frame) | |||
local tpl_args = getArgs(frame, { | |||
parentFirst = true | |||
}) | |||
frame = m_util.misc.get_frame(frame) | |||
local dcl_args = {} | |||
dcl_args._table = data.table | |||
for k, field_data in pairs(data.fields) do | |||
if field_data.field then | |||
dcl_args[field_data.field] = field_data.type | |||
for _, stat_data in pairs(core.stat_map) do | |||
if stat_data.field == k then | |||
for _, range_fields in ipairs(h.range_fields) do | |||
dcl_args[stat_data.field .. range_fields.field] = range_fields.type | |||
end | |||
break | |||
end | |||
end | |||
end | |||
end | |||
if dcl_args._table == 'weapons' then | |||
for _, dps_data in ipairs(core.dps_map) do | |||
for _, range_fields in ipairs(h.range_fields) do | |||
dcl_args[dps_data.field .. range_fields.field] = range_fields.type | |||
end | |||
end | |||
end | |||
if tpl_args.debug then | |||
mw.logObject(dcl_args) | |||
end | |||
return m_util.cargo.declare(frame, dcl_args) | |||
end | |||
end | |||
p.table_items = cargo_declare(core.cargo.items) | |||
p.table_item_purchase_costs = cargo_declare(core.cargo.item_purchase_costs) | |||
p.table_item_stats = cargo_declare(core.cargo.item_stats) | |||
p.table_item_buffs = cargo_declare(core.cargo.item_buffs) | |||
p.table_upgraded_from_sets = cargo_declare(core.cargo.upgraded_from_sets) | |||
p.table_upgraded_from_groups = cargo_declare(core.cargo.upgraded_from_groups) | |||
p.table_amulets = cargo_declare(core.cargo.amulets) | |||
p.table_flasks = cargo_declare(core.cargo.flasks) | |||
p.table_weapons = cargo_declare(core.cargo.weapons) | |||
p.table_armours = cargo_declare(core.cargo.armours) | |||
p.table_shields = cargo_declare(core.cargo.shields) | |||
p.table_skill_gems = cargo_declare(core.cargo.skill_gems) | |||
p.table_maps = cargo_declare(core.cargo.maps) | |||
p.table_stackables = cargo_declare(core.cargo.stackables) | |||
p.table_essences = cargo_declare(core.cargo.essences) | |||
p.table_hideout_doodads = cargo_declare(core.cargo.hideout_doodads) | |||
p.table_prophecies = cargo_declare(core.cargo.prophecies) | |||
p.table_divination_cards = cargo_declare(core.cargo.divination_cards) | |||
p.table_jewels = cargo_declare(core.cargo.jewels) | |||
-- ---------------------------------------------------------------------------- | |||
-- Page views | |||
-- ---------------------------------------------------------------------------- | |||
-- | |||
-- Template:Item | |||
-- | |||
function p. | function p.itembox (frame) | ||
-- | -- | ||
-- Args/Frame | -- Args/Frame | ||
-- | -- | ||
local t = os.clock() | |||
local tpl_args = getArgs(frame, { | local tpl_args = getArgs(frame, { | ||
parentFirst = true | parentFirst = true | ||
}) | }) | ||
frame = m_util.misc.get_frame(frame) | frame = m_util.misc.get_frame(frame) | ||
-- | -- | ||
tpl_args. | -- Shared args | ||
if tpl_args. | -- | ||
tpl_args. | |||
tpl_args._flags = {} | |||
tpl_args._base_item_args = {} | |||
tpl_args._mods = {} | |||
tpl_args._stats = {} | |||
tpl_args._implicit_stats = {} | |||
tpl_args._explicit_stats = {} | |||
tpl_args._subobjects = {} | |||
tpl_args._properties = {} | |||
tpl_args._errors = {} | |||
core.build_item_classes(tpl_args, frame) | |||
core.build_cargo_data(tpl_args, frame) | |||
-- Using general purpose function to handle release and removal versions | |||
m_util.args.version(tpl_args, {frame=frame, set_properties=true}) | |||
-- Must validate some argument early. It is required for future things | |||
core.process_arguments(tpl_args, frame, {array=core.default_args}) | |||
core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].args}) | |||
-- Base Item | |||
core.process_base_item(tpl_args, frame) | |||
-- Prophecy special snowflake | |||
if tpl_args.base_item == 'Prophecy' then | |||
err = core.process_arguments(tpl_args, frame, {array=core.prophecy_args}) | |||
if err then | |||
return err | |||
end | |||
tpl_args.inventory_icon = string.format(i18n.inventory_icon, 'Prophecy') | |||
end | end | ||
-- Mods | |||
for _, k in ipairs({'implicit', 'explicit'}) do | |||
local success = true | |||
local i = 1 | |||
while success do | |||
success = core.validate_mod(tpl_args, frame, {key=k, i=i}) | |||
i = i + 1 | |||
end | |||
end | end | ||
tpl_args. | core.process_smw_mods(tpl_args, frame) | ||
-- Add stats - this is for when mods are not set, but we still need stats to calcuate new armour values etc | |||
m_util.args.stats(tpl_args, {prefix='extra_'}) | |||
for _, stat in ipairs(tpl_args.extra_stats) do | |||
if stat.value ~= nil then | |||
stat.min = stat.value | |||
stat.max = stat.value | |||
stat.avg = stat.value | |||
end | |||
h.stats_update(tpl_args, stat.id, stat, nil, '_stats') | |||
h.stats_update(tpl_args, stat.id, stat, nil, '_explicit_stats') | |||
end | |||
-- Transpose stats into cargo data | |||
for id, data in pairs(tpl_args._stats) do | |||
tpl_args._subobjects[#tpl_args._subobjects+1] = { | |||
_table = 'item_stats', | |||
id = id, | |||
min = data.min, | |||
max = data.max, | |||
avg = data.avg, | |||
is_implicit = nil, | |||
} | |||
end | |||
for id, data in pairs(tpl_args._explicit_stats) do | |||
tpl_args._subobjects[#tpl_args._subobjects+1] = { | |||
_table = 'item_stats', | |||
id = id, | |||
min = data.min, | |||
max = data.max, | |||
avg = data.avg, | |||
is_implicit = false, | |||
} | |||
end | |||
for id, data in pairs(tpl_args._implicit_stats) do | |||
tpl_args._subobjects[#tpl_args._subobjects+1] = { | |||
_table = 'item_stats', | |||
id = id, | |||
min = data.min, | |||
max = data.max, | |||
avg = data.avg, | |||
is_implicit = true, | |||
} | |||
end | |||
-- Handle extra stats (for gems) | |||
if | if core.class_groups.gems.keys[tpl_args.class] then | ||
local | m_skill.skill(frame, tpl_args) | ||
end | |||
-- | |||
-- Handle local stats increases/reductions/additions | |||
-- | |||
local skip = {} | |||
-- general stats | |||
for k, data in pairs(core.stat_map) do | |||
local value = tpl_args[k] | |||
if tpl_args. | if value ~= nil and skip[k] == nil then | ||
value = {min=value, max=value, base=value} | |||
-- If stats are overriden we scan save some CPU time here | |||
local overridden = false | |||
if data.stats_override ~= nil then | |||
for stat_id, override_value in pairs(data.stats_override) do | |||
local stat_value = tpl_args._stats[stat_id] | |||
if stat_value ~= nil then | |||
-- Use the value of stat | |||
if override_value == true then | |||
value.min = stat_value.min | |||
value.max = stat_value.max | |||
overridden = true | |||
elseif stat_value ~= 0 then | |||
value.min = override_value.min | |||
value.max = override_value.max | |||
overridden = true | |||
end | |||
end | |||
end | |||
end | |||
if overridden == false then | |||
-- The simple cases; this must be using ipairs as "add" must apply before | |||
for _, operator in ipairs({'add', 'more'}) do | |||
local st = data['stats_' .. operator] | |||
if st ~= nil then | |||
for _, statid in ipairs(st) do | |||
if tpl_args._stats[statid] ~= nil then | |||
h.stat[operator](value, tpl_args._stats[statid]) | |||
end | |||
end | |||
end | |||
end | |||
-- For increased stats we need to add them up first | |||
for stat_key, stat_func in pairs({increased=h.stat.more, increased_inverse=h.stat.more_inverse}) do | |||
local st = data['stats_' .. stat_key] | |||
if st ~= nil then | |||
local total_increase = {min=0, max=0} | |||
for _, statid in ipairs(st) do | |||
if tpl_args._stats[statid] ~= nil then | |||
for var, current_value in pairs(total_increase) do | |||
total_increase[var] = current_value + tpl_args._stats[statid][var] | |||
end | |||
end | |||
end | |||
stat_func(value, total_increase) | |||
end | |||
end | |||
if data.minimum ~= nil then | |||
for _, key in ipairs({'min', 'max'}) do | |||
if value[key] < data.minimum then | |||
value[key] = data.minimum | |||
end | |||
end | |||
end | |||
else | |||
end | |||
value.avg = (value.min + value.max) / 2 | |||
-- don't add the properties unless we need to | |||
if (data.default ~= nil and (value.min ~= data.default or value.max ~= data.default)) or data.default == nil then | |||
for short_key, range_data in pairs(h.range_map) do | |||
tpl_args[data.field .. range_data.var] = value[short_key] | |||
end | |||
-- process to HTML to use on list pages or other purposes | |||
h.handle_range_args(tpl_args, frame, k, data.field, value, data.html_fmt_options or {}) | |||
end | end | ||
for short_key, range_data in pairs(h.range_map) do | |||
tpl_args[k .. range_data.var] = value[short_key] | |||
end | |||
end | |||
end | |||
-- calculate and handle weapon dps | |||
if core.class_groups.weapons.keys[tpl_args.class] then | |||
for _, data in ipairs(core.dps_map) do | |||
local damage = { | |||
min = {}, | |||
max = {}, | |||
} | |||
for var_type, value in pairs(damage) do | |||
-- covers the min/max/avg range | |||
for short_key, range_data in pairs(h.range_map) do | |||
value[short_key] = 0 | |||
for _, damage_key in ipairs(data.damage_args) do | |||
value[short_key] = value[short_key] + (tpl_args[string.format('%s_%s%s', damage_key, var_type, range_data.var)] or 0) | |||
end | |||
end | |||
end | |||
local value = {} | |||
for short_key, range_data in pairs(h.range_map) do | |||
local result = (damage.min[short_key] + damage.max[short_key]) / 2 * tpl_args[string.format('attack_speed%s', range_data.var)] | |||
value[short_key] = result | |||
tpl_args[string.format('%s%s', data.field, range_data.var)] = result | |||
end | |||
if | if value.avg > 0 then | ||
h.handle_range_args(tpl_args, frame, data.name, data.field, value, data.html_fmt_options or {}) | |||
end | end | ||
end | |||
end | |||
-- late processing | |||
core.process_arguments(tpl_args, frame, {array=core.late_args}) | |||
core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].late_args}) | |||
-- Handle upgrade from restrictions/info | |||
core.process_upgraded_from(tpl_args, frame) | |||
-- ------------------------------------------------------------------------ | |||
-- Infobox handling | |||
-- ------------------------------------------------------------------------ | |||
local extra_class = '' | |||
local container = mw.html.create('span') | |||
:attr( 'class', 'item-box -' .. tpl_args.frame_type) | |||
if tpl_args.class == 'Divination Card' then | |||
container | |||
:tag('span') | |||
:attr( 'class', 'divicard-wrapper') | |||
:tag('span') | |||
:attr('class', 'divicard-art') | |||
:wikitext( '[[' .. tpl_args.card_art .. '|link=|alt=]]' ) | |||
:done() | |||
:tag('span') | |||
:attr('class', 'divicard-frame') | |||
:wikitext( '[[File:Divination card frame.png|link=|alt=]]' ) | |||
:done() | |||
:tag('span') | |||
:attr('class', 'divicard-header') | |||
:wikitext(tpl_args.name) | |||
:done() | |||
:tag('span') | |||
:attr('class', 'divicard-stack') | |||
:wikitext(tpl_args.stack_size) | |||
:done() | |||
:tag('span') | |||
:attr('class', 'divicard-reward') | |||
:tag('span') | |||
:wikitext(tpl_args.description) | |||
:done() | |||
:done() | |||
:tag('span') | |||
:attr('class', 'divicard-flavour text-color -flavour') | |||
:tag('span') | |||
:wikitext(tpl_args.flavour_text) | |||
:done() | |||
:done() | |||
:done() | |||
--TODO Extras? | |||
else | |||
local header_css | |||
if tpl_args.base_item and tpl_args.rarity ~= 'Normal' then | |||
line_type = 'double' | |||
else | |||
line_type = 'single' | |||
end | end | ||
local name_line = tpl_args.name | |||
if tpl_args.base_item and tpl_args.base_item ~= 'Prophecy' then | |||
name_line = name_line .. '<br>' .. tpl_args.base_item | |||
end | |||
-- | container | ||
:tag('span') | |||
:attr( 'class', 'header -' .. line_type ) | |||
:wikitext( name_line ) | |||
:done() | |||
core.display.add_to_container_from_map(tpl_args, frame, container, core.item_display_groups) | |||
end | |||
if tpl_args.skill_icon ~= nil then | |||
container:wikitext(string.format('[[%s]]', tpl_args.skill_icon)) | |||
end | |||
-- Store the infobox so it can be accessed with ease on other pages | |||
tpl_args.html = tostring(container) | |||
if tpl_args.inventory_icon ~= nil and tpl_args.class ~= 'Divination Card' then | |||
container:wikitext(string.format('[[%s|%sx%spx]]', tpl_args.inventory_icon, c.image_size_full*tpl_args.size_x, c.image_size_full*tpl_args.size_y)) | |||
end | |||
-- | |||
-- Secondary infobox | |||
-- | |||
local extra_infobox = mw.html.create('span') | |||
:attr( 'class', 'item-box -' .. tpl_args.frame_type) | |||
local | core.display.add_to_container_from_map(tpl_args, frame, extra_infobox, core.extra_display_groups) | ||
if # | |||
-- | |||
-- Output | |||
tpl_args. | -- | ||
)} | |||
local infobox = mw.html.create('span') | |||
infobox | |||
:attr('class', 'infobox-page-container') | |||
:node(container) | |||
) | :node(extra_infobox) | ||
if tpl_args.skill_screenshot then | |||
infobox:wikitext(string.format('<br>[[%s|300px]]', tpl_args.skill_screenshot)) | |||
end | |||
local out = tostring(infobox) | |||
-- ------------------------------------------------------------------------ | |||
-- Category handling | |||
-- ------------------------------------------------------------------------ | |||
local cats = {} | |||
if tpl_args.rarity == 'Unique' then | |||
cats[#cats+1] = string.format(i18n.categories.unique_affix, tpl_args.class) | |||
elseif tpl_args.base_item == 'Prophecy' then | |||
cats[#cats+1] = i18n.categories.prophecies | |||
elseif tpl_args.is_talisman then | |||
cats[#cats+1] = i18n.categories.talismans | |||
elseif tpl_args.is_essence then | |||
cats[#cats+1] = i18n.categories.essences | |||
else | |||
cats[#cats+1] = tpl_args.class | |||
end | |||
for _, attr in ipairs(m_game.constants.attributes) do | |||
if tpl_args[attr.long_lower .. '_percent'] then | |||
cats[#cats+1] = string.format('%s %s', attr.long_upper, tpl_args.class) | |||
end | |||
end | |||
local affix | |||
if tpl_args.class == 'Active Skill Gems' or tpl_args.class == 'Support Skill Gems' then | |||
affix = i18n.categories.gem_tag_affix | |||
end | |||
if affix ~= nil then | |||
for _, tag in ipairs(tpl_args.gem_tags) do | |||
cats[#cats+1] = string.format(affix, tag) | |||
end | |||
end | |||
if #tpl_args.alternate_art_inventory_icons > 0 then | |||
cats[#cats+1] = i18n.categories.alternate_artwork | |||
end | |||
-- TODO: add maintenance categories | |||
if tpl_args.release_version == nil then | |||
cats[#cats+1] = i18n.categories.missing_release_version | |||
end | |||
if tpl_args._flags.text_modifier then | |||
cats[#cats+1] = i18n.categories.improper_modifiers | |||
end | |||
if tpl_args._flags.broken_upgraded_from_reference then | |||
cats[#cats+1] = i18n.categories.broken_upgraded_from_reference | |||
end | |||
out = out .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug}) | |||
-- | |||
-- Misc | |||
-- | |||
-- Also show the infobox for areas right away for maps, since they're both on the same page | |||
local query_id | |||
if tpl_args.rarity == 'Normal' and tpl_args.map_area_id ~= nil then | |||
query_id = tpl_args.map_area_id | |||
elseif tpl_args.rarity == 'Unique' and unique_map_area_id ~= nil then | |||
local query_id = tpl_args.unique_map_area_id | |||
end | |||
if query_id then | |||
out = out .. m_area.query_area_info{cats=yes, where=string.format('areas.id="%s"', query_id)} | |||
end | |||
-- ------------------------------------------------------------------------ | |||
-- Store cargo data | |||
-- ------------------------------------------------------------------------ | |||
-- Map argument values for cargo storage | |||
for _, table_name in ipairs(core.item_classes[tpl_args.class].tables) do | |||
tpl_args._subobjects[table_name] = { | |||
_table = table_name, | |||
} | |||
end | |||
for k, v in pairs(tpl_args) do | |||
local data = core.map[k] | |||
if data ~= nil then | |||
if data.table ~= nil and data.field ~= nil then | |||
tpl_args._subobjects[data.table][data.field] = v | |||
elseif data.table == nil and data.field ~= nil then | |||
error(string.format('Missing table for field "%s", key "%s", value "%s", data:\n%s', data.field, k, v, mw.dumpObject(data))) | |||
elseif data.table ~= nil and data.field == nil then | |||
error(string.format('Missing field for table "%s", key "%s", value "%s", data:\n%s', data.table, k, v, mw.dumpObject(data))) | |||
end | |||
end | |||
end | |||
for _, data in pairs(tpl_args._subobjects) do | |||
m_util.cargo.store(frame, data) | |||
end | |||
mw.logObject(os.clock() - t) | |||
-- Show additional error messages in console to help fixing them | |||
mw.logObject(table.concat(tpl_args._errors, '\n')) | |||
return out | |||
end | |||
-- ---------------------------------------------------------------------------- | |||
-- Result formatting templates for SMW queries | |||
-- ---------------------------------------------------------------------------- | |||
-- | |||
-- Template: | |||
-- | |||
function p.simple_item_list(frame) | |||
-- Args | |||
local tpl_args = getArgs(frame, { | |||
parentFirst = true | |||
}) | |||
frame = m_util.misc.get_frame(frame) | |||
local query = {} | |||
for key, value in pairs(tpl_args) do | |||
if string.sub(key, 0, 2) == 'q_' then | |||
query[string.sub(key, 3)] = value | |||
end | |||
end | |||
local fields = { | |||
'items._pageName', | |||
'items.name', | |||
} | |||
if tpl_args.no_icon == nil then | |||
fields[#fields+1] = 'items.inventory_icon' | |||
end | |||
if tpl_args.no_html == nil then | |||
fields[#fields+1] = 'items.html' | |||
end | |||
local results = m_util.cargo.query( | |||
{'items'}, | |||
fields, | |||
query | |||
) | |||
local out = {} | |||
for _, row in ipairs(results) do | |||
local link = f_item_link{page=tpl_args['items._pageName'], name=tpl_args['items.name'], inventory_icon=tpl_args['items.inventory_icon'] or '', html=tpl_args['items.html'] or '', skip_query=true} | |||
if args.format == nil then | |||
out[#out+1] = string.format('* %s', link) | |||
elseif args.format == 'none' then | |||
out[#out+1] = link | |||
elseif args.format == 'li' then | |||
out[#out+1] = string.format('<li>%s</li>', link) | |||
else | |||
error(string.format(i18n.errors.generic_argument_parameter, 'format', args.format)) | |||
end | |||
end | |||
if args.format == nil then | |||
return table.concat(out, '\n') | |||
elseif args.format == 'none' then | |||
return table.concat(out, '\n') | |||
elseif args.format == 'li' then | |||
return table.concat(out) | |||
end | |||
end | |||
-- ---------------------------------------------------------------------------- | |||
-- Reponsibile for subtemplates of Template:SMW item table | |||
-- | |||
function p.item_table(frame) | |||
-- args | |||
local tpl_args = getArgs(frame, { | |||
parentFirst = true | |||
}) | |||
frame = m_util.misc.get_frame(frame) | |||
local modes = { | |||
skill = { | |||
data = core.result.skill_gem_new, | |||
header = i18n.item_table.skill_gem, | |||
}, | |||
item = { | |||
data = core.result.generic_item, | |||
header = i18n.item_table.item, | |||
}, | |||
} | |||
if tpl_args.mode == nil then | |||
tpl_args.mode = 'item' | |||
end | |||
if modes[tpl_args.mode] == nil then | |||
error(i18n.errors.invalid_item_table_mode) | |||
end | |||
local row_infos = {} | |||
for _, row_info in ipairs(modes[tpl_args.mode].data) do | |||
local enabled = false | |||
if row_info.arg == nil then | |||
enabled = true | |||
elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then | |||
enabled = true | |||
elseif type(row_info.arg) == 'table' then | |||
for _, argument in ipairs(row_info.arg) do | |||
if m_util.cast.boolean(tpl_args[argument]) then | |||
enabled = true | |||
break | |||
end | |||
end | |||
end | end | ||
if | if enabled then | ||
row_info.options = row_info.options or {} | |||
row_infos[#row_infos+1] = row_info | |||
end | end | ||
end | |||
-- Parse stat arguments | |||
local stat_columns = {} | |||
local query_stats = {} | |||
local stat_results = {} | |||
local i = 0 | |||
repeat | |||
i = i + 1 | |||
local prefix = string.format('stat_column%s_', i) | |||
local col_info = { | |||
header = tpl_args[prefix .. 'header'] or tostring(i), | |||
format = tpl_args[prefix .. 'format'], | |||
stat_format = tpl_args[prefix .. 'stat_format'] or 'separate', | |||
order = tonumber(tpl_args[prefix .. 'order']) or (10000000 + i), | |||
stats = {}, | |||
options = {}, | |||
} | |||
local j = 0 | |||
repeat | |||
j = j +1 | |||
local stat_info = { | |||
id = tpl_args[string.format('%sstat%s_id', prefix, j)], | |||
} | |||
if stat_info.id then | |||
col_info.stats[#col_info.stats+1] = stat_info | |||
query_stats[stat_info.id] = {} | |||
else | |||
-- Stop iteration entirely if this was the first index but no stat was supplied. We assume that we stop in this case. | |||
if j == 1 then | |||
i = nil | |||
end | |||
-- stop iteration | |||
j = nil | |||
end | |||
until j == nil | |||
-- Don't add this column if no stats were provided. | |||
if #col_info.stats > 0 then | |||
stat_columns[#stat_columns+1] = col_info | |||
end | |||
until i == nil | |||
for _, col_info in ipairs(stat_columns) do | |||
local row_info = { | |||
--arg | |||
header = col_info.header, | |||
fields = {}, | |||
display = function(tr, data, properties) | |||
if col_info.stat_format == 'separate' then | |||
local stat_texts = {} | |||
local num_stats = 0 | |||
local vmax = 0 | |||
for _, stat_info in ipairs(col_info.stats) do | |||
num_stats = num_stats + 1 | |||
-- stat results from outside body | |||
local stat = (stat_results[data['items._pageName']] or {})[stat_info.id] | |||
if stat ~= nil then | |||
stat_texts[#stat_texts+1] = h.format_value(tpl_args, frame, stat, {no_color=true}) | |||
vmax = vmax + stat.max | |||
end | |||
end | |||
if num_stats ~= #stat_texts then | |||
tr:wikitext(m_util.html.td.na()) | |||
else | |||
local text | |||
if col_info.format then | |||
text = string.format(col_info.format, unpack(stat_texts)) | |||
else | |||
text = table.concat(stat_texts, ', ') | |||
end | |||
tr:tag('td') | |||
:attr('data-sort-value', vmax) | |||
:attr('class', 'tc -mod') | |||
:wikitext(text) | |||
end | |||
elseif col_info.stat_format == 'add' then | |||
local total_stat = { | |||
min = 0, | |||
max = 0, | |||
avg = 0, | |||
} | |||
for _, stat_info in ipairs(col_info.stats) do | |||
local stat = (stat_results[data['items._pageName']] or {})[stat_info.id] | |||
if stat ~= nil then | |||
for k, v in pairs(total_stat) do | |||
total_stat[k] = v + stat[k] | |||
end | |||
end | |||
end | |||
if col_info.format == nil then | |||
col_info.format = '%s' | |||
end | |||
tr:tag('td') | |||
:attr('data-sort-value', total_stat.max) | |||
:attr('class', 'tc -mod') | |||
:wikitext(string.format(col_info.format, h.format_value(tpl_args, frame, total_stat, {no_color=true}))) | |||
else | |||
error(string.format(i18n.errors.generic_argument_parameter, 'stat_format', col_info.stat_format)) | |||
end | |||
end, | |||
order = col_info.order, | |||
} | |||
table.insert(row_infos, row_info) | |||
end | |||
-- sort the rows | |||
table.sort(row_infos, function (a, b) | |||
return (a.order or 0) < (b.order or 0) | |||
end) | |||
-- Parse query arguments | |||
local tables_assoc = {items=true} | |||
local fields = { | |||
'items._pageName', | |||
'items.name', | |||
'items.inventory_icon', | |||
'items.html', | |||
'items.size_x', | |||
'items.size_y', | |||
} | |||
-- | |||
local prepend = { | |||
q_join=true, | |||
q_groupBy=true, | |||
q_tables=true | |||
} | |||
local query = {} | |||
for key, value in pairs(tpl_args) do | |||
if string.sub(key, 0, 2) == 'q_' then | |||
if prepend[key] then | |||
value = ',' .. value | |||
end | |||
query[string.sub(key, 3)] = value | |||
end | |||
end | end | ||
for | --query.limit = 5000 | ||
if | |||
for _, rowinfo in ipairs(row_infos) do | |||
if type(rowinfo.fields) == 'function' then | |||
rowinfo.fields = rowinfo.fields() | |||
end | |||
for index, field in ipairs(rowinfo.fields) do | |||
rowinfo.options[index] = rowinfo.options[index] or {} | |||
fields[#fields+1] = field | |||
tables_assoc[m_util.string.split(field, '%.')[1]] = true | |||
end | end | ||
end | end | ||
-- reformat the fields & tables so they can be retrieved correctly | |||
for index, field in ipairs(fields) do | |||
fields[index] = string.format('%s=%s', field, field) | |||
end | |||
local tables = {} | |||
for table_name,_ in pairs(tables_assoc) do | |||
tables[#tables+1] = table_name | |||
end | |||
-- take care of required joins according to the tables | |||
local joins = {} | |||
for index, table_name in ipairs(tables) do | |||
if table_name ~= 'items' then | |||
joins[#joins+1] = string.format('items._pageID=%s._pageID', table_name) | |||
end | end | ||
end | |||
query.join = table.concat(joins, ',') .. (query.join or '') | |||
-- Cargo workaround: avoid duplicates using groupBy | |||
query.groupBy = 'items._pageID' .. (query.groupBy or '') | |||
local results = cargo.query( | |||
table.concat(tables,',') .. (tpl_args.q_tables or ''), | |||
table.concat(fields,','), | |||
query | |||
) | |||
if #results == 0 and tpl_args.default ~= nil then | |||
return tpl_args.default | |||
end | |||
if #stat_columns > 0 then | |||
local continue = true | |||
local offset = 0 | |||
while continue do | |||
if tpl_args.q_where then | |||
tpl_args.q_where = string.format(' AND (%s)', tpl_args.q_where) | |||
else | |||
tpl_args.q_where = '' | |||
end | |||
local temp = cargo.query( | |||
'items,item_stats' .. (tpl_args.q_tables or ''), | |||
'item_stats._pageName=item_stats._pageName, item_stats.id=item_stats.id, item_stats.min=item_stats.min, item_stats.avg=item_stats.avg, item_stats.max=item_stats.max', | |||
{ | |||
where='item_stats.is_implicit IS NULL' .. tpl_args.q_where, | |||
join='items._pageID=item_stats._pageID' .. (tpl_args.q_join or ''), | |||
limit=5000, | |||
--offset = offset, | |||
} | |||
) | |||
for _, | for _, row in ipairs(temp) do | ||
if | if query_stats[row['item_stats.id']] ~= nil then | ||
local stat = { | |||
min = tonumber(row['item_stats.min']), | |||
max = tonumber(row['item_stats.max']), | |||
avg = tonumber(row['item_stats.avg']), | |||
} | |||
if stat_results[row['item_stats._pageName']] == nil then | |||
stat_results[row['item_stats._pageName']] = {[row['item_stats.id']] = stat} | |||
else | |||
stat_results[row['item_stats._pageName']][row['item_stats.id']] = stat | |||
end | |||
end | end | ||
end | end | ||
-- Cargo doesnt support offset yet | |||
if #temp == 5000 then | |||
--TODO: Cargo | |||
error('Stats > 5000') | |||
--offset = offset + 5000 | |||
else | |||
continue = false | |||
end | |||
end | |||
end | |||
local tbl = mw.html.create('table') | |||
tbl:attr('class', 'wikitable sortable item-table') | |||
-- Header | |||
local tr = tbl:tag('tr') | |||
tr | |||
:tag('th') | |||
:wikitext(modes[tpl_args.mode].header) | |||
:done() | |||
for _, row_info in ipairs(row_infos) do | |||
tr | |||
:tag('th') | |||
:attr('data-sort-type', row_info.sort_type or 'number') | |||
:wikitext(row_info.header) | |||
:done() | |||
end | |||
for _, row in ipairs(results) do | |||
tr = tbl:tag('tr') | |||
local il_args = { | |||
skip_query=true, | |||
page=row['items._pageName'], | |||
name=row['items.name'], | |||
inventory_icon=row['items.inventory_icon'], | |||
html=row['items.html'], | |||
width=row['items.size_x'], | |||
height=row['items.size_y'], | |||
} | |||
if tpl_args.large then | |||
il_args.large = tpl_args.large | |||
end | end | ||
if | tr | ||
:tag('td') | |||
:wikitext(f_item_link(il_args)) | |||
:done() | |||
for _, rowinfo in ipairs(row_infos) do | |||
-- this has been cast from a function in an earlier step | |||
local display = true | |||
for index, field in ipairs(rowinfo.fields) do | |||
-- this will bet set to an empty value not nil confusingly | |||
if row[field] == '' then | |||
if rowinfo.options[index].optional ~= true then | |||
display = false | |||
break | |||
else | |||
row[field] = nil | |||
end | |||
end | |||
end | |||
if display then | |||
rowinfo.display(tr, row, rowinfo.fields) | |||
else | |||
tr:wikitext(m_util.html.td.na()) | |||
end | |||
end | end | ||
end | end | ||
-- | return tostring(tbl) | ||
end | |||
item_table_factory = {} | |||
function item_table_factory.intro(args) | |||
-- args: | |||
-- data_array | |||
-- header | |||
local | return function (frame) | ||
-- Args | |||
local tpl_args = getArgs(frame, { | |||
parentFirst = true | |||
}) | |||
frame = m_util.misc.get_frame(frame) | |||
-- | |||
tpl_args.userparam = m_util.string.split_args(tpl_args.userparam, {sep=', '}) | |||
local tr = mw.html.create('tr') | |||
tr | |||
:tag('th') | |||
:wikitext(args.header) | |||
:done() | |||
for _, rowinfo in ipairs(args.data_array) do | |||
if rowinfo.arg == nil or tpl_args.userparam[rowinfo.arg] then | |||
tr | |||
:tag('th') | |||
:wikitext(rowinfo.header) | |||
:done() | |||
end | |||
end | |||
return '<table class="wikitable sortable item-table">' .. tostring(tr) | |||
end | |||
end | |||
-- ---------------------------------------------------------------------------- | |||
-- Item lists | |||
-- ---------------------------------------------------------------------------- | |||
function p.skill_gem_list_by_gem_tag(frame) | |||
-- Args | |||
local tpl_args = getArgs(frame, { | |||
parentFirst = true | |||
}) | |||
frame = m_util.misc.get_frame(frame) | |||
if tpl_args.class == 'Support Skill Gems' then | |||
elseif tpl_args.class == 'Active Skill Gems' then | |||
else | |||
error(i18n.errors.invalid_item_class) | |||
end | |||
local query = {} | |||
query[#query+1] = string.format('[[Has item class::%s]]', tpl_args.class) | |||
query[#query+1] = '?Has gem tags' | |||
query[#query+1] = '?Has name' | |||
query[#query+1] = '?Has inventory icon' | |||
--query[#query+1] = '?Has infobox HTML' | |||
query.limit = 5000 | |||
query.sort = 'Has name' | |||
local results = m_util.smw.query(query, frame) | |||
local tags = {} | |||
for _, row in ipairs(results) do | |||
row['Has gem tags'] = m_util.string.split(row['Has gem tags'], '<MANY>') | |||
for _, tag in ipairs(row['Has gem tags']) do | |||
if tags[tag] == nil then | |||
tags[tag] = {} | |||
end | |||
table.insert(tags[tag], row) | |||
end | |||
end | end | ||
local | local tags_sorted = {} | ||
for tag, _ in pairs(tags) do | |||
table.insert(tags_sorted, tag) | |||
end | |||
table.sort(tags_sorted) | |||
local tbl = mw.html.create('table') | |||
tbl | |||
:attr('class', 'wikitable sortable') | |||
:tag('tr') | |||
:tag('th') | |||
:wikitext('Tag') | |||
:done() | |||
:tag('th') | |||
:wikitext('Skills') | |||
:done() | |||
:done() | |||
for _, tag in ipairs(tags_sorted) do | |||
local rows = tags[tag] | |||
local tr = tbl:tag('tr') | |||
tr | |||
:tag('td') | |||
:wikitext(tag) | |||
local td = tr:tag('td') | |||
for i, row in ipairs(rows) do | |||
td:wikitext(f_item_link{page=row[1], name=row['Has Name'], inventory_icon=row['Has inventory icon'], html=row['Has infobox HTML'] or ''}) | |||
if i < #rows then | |||
td:wikitext('<br>') | |||
end | |||
end | |||
end | |||
return tostring(tbl) | |||
end | |||
if | -- ---------------------------------------------------------------------------- | ||
-- Misc. Item templates | |||
-- ---------------------------------------------------------------------------- | |||
-- | |||
-- Template: Item acquisition | |||
-- | |||
-- Used to duplicate the information from the infobox in a more readable manner on the page. | |||
function p.item_acquisition (frame) | |||
-- Get args | |||
local tpl_args = getArgs(frame, { | |||
parentFirst = true | |||
}) | |||
frame = m_util.misc.get_frame(frame) | |||
tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle()) | |||
local out = {} | |||
local results | |||
local query | |||
-- ------------------------- | |||
-- Drop restrictions by area | |||
-- ------------------------- | |||
results = m_util.cargo.query( | |||
{'items'}, | |||
{'items.drop_areas_html'}, | |||
{ | |||
where=string.format('items._pageName="%s"', tpl_args.page), | |||
-- Workaround: Fix cargo duplicates | |||
groupBy='items._pageID', | |||
} | |||
) | |||
if #results > 0 then | |||
results = results[1] | |||
if results['items.drop_areas_html'] then | |||
local ul = mw.html.create('ul') | |||
for _, area in ipairs(m_util.string.split(results['items.drop_areas_html'], ',%s*')) do | |||
ul:tag('li') | |||
:wikitext(area) | |||
end | |||
out[#out+1] = i18n.acquisition.area | |||
out[#out+1]= '<br>' | |||
out[#out+1] = tostring(ul) | |||
end | |||
end | |||
-- ------------------------------------ | |||
-- Obtained via vendor recipes/upgrades | |||
-- ------------------------------------ | |||
-- | |||
-- Query data | |||
-- | |||
local sets = {} | |||
results = m_util.cargo.query( | |||
{'upgraded_from_sets'}, | |||
{ | |||
'upgraded_from_sets.set_id', | |||
'upgraded_from_sets.text', | |||
}, | |||
{ | |||
where=string.format('upgraded_from_sets._pageName="%s"', tpl_args.page), | |||
-- Workaround: Fix cargo duplicates | |||
groupBy='upgraded_from_sets._pageID,upgraded_from_sets.set_id', | |||
} | |||
) | |||
for _, row in ipairs(results) do | |||
row.groups = {} | |||
sets[tonumber(row['upgraded_from_sets.set_id'])] = row | |||
end | |||
results = m_util.cargo.query( | |||
{'upgraded_from_groups'}, | |||
{ | |||
'upgraded_from_groups.set_id', | |||
'upgraded_from_groups.group_id', | |||
'upgraded_from_groups.notes', | |||
'upgraded_from_groups.amount', | |||
'upgraded_from_groups.item_name', | |||
'upgraded_from_groups.item_page', | |||
}, | |||
{ | |||
where=string.format('upgraded_from_groups._pageName="%s"', tpl_args.page), | |||
-- Workaround: Fix cargo duplicates | |||
groupBy='upgraded_from_groups._pageID,upgraded_from_groups.set_id,upgraded_from_groups.group_id', | |||
} | |||
) | |||
for _, row in ipairs(results) do | |||
sets[tonumber(row['upgraded_from_groups.set_id'])].groups[tonumber(row['upgraded_from_groups.group_id'])] = row | |||
end | |||
-- | |||
-- Build output | |||
-- | |||
if #sets > 0 then | |||
local ul = mw.html.create('ul') | |||
for _, set in ipairs(sets) do | |||
local li = ul:tag('li') | |||
if set['upgraded_from_sets.text'] then | |||
li:wikitext(set['upgraded_from_sets.text'] .. '<br>') | |||
end | |||
local str = {} | |||
for _, group in ipairs(set.groups) do | |||
str[#str+1] = string.format('%sx [[%s|%s]]', group['upgraded_from_groups.amount'], group['upgraded_from_groups.item_page'], group['upgraded_from_groups.item_name'] or group['upgraded_from_groups.item_page']) | |||
end | |||
li:wikitext(table.concat(str, ', ')) | |||
end | end | ||
out[#out+1] = i18n.acquisition.upgraded_from | |||
out[#out+1]= '<br>' | |||
out[#out+1] = tostring(ul) | |||
end | |||
out[#out+1] = tpl_args.acquisition_insert | |||
-- ------------------------------------- | |||
-- Ingredient of vendor recipes/upgrades | |||
-- ------------------------------------- | |||
-- | |||
-- Query | |||
-- | |||
-- TODO: IL links | |||
results = m_util.cargo.query( | |||
{'upgraded_from_groups'}, | |||
{'upgraded_from_groups._pageName'}, | |||
{ | |||
where=string.format('upgraded_from_groups.item_page="%s"', tpl_args.page), | |||
-- Only need each page name once | |||
groupBy='upgraded_from_groups._pageName', | |||
} | |||
) | |||
if #results > 0 then | |||
local head = mw.html.create('h3') | |||
head:wikitext(i18n.acquisition.ingredient_header) | |||
out[#out+1] = tostring(head) | |||
out[#out+1] = i18n.acquisition.ingredient | |||
out[#out+1]= '<br>' | |||
local ul = mw.html.create('ul') | |||
for _, row in ipairs(results) do | |||
ul:tag('li') | |||
:wikitext(string.format('[[%s]]', row['upgraded_from_groups._pageName'])) | |||
end | |||
out[#out+1] = tostring(ul) | |||
end | |||
out[#out+1] = tpl_args.ingredient_append | |||
-- ------------------------------------ | |||
-- output | |||
-- ------------------------------------ | |||
local head = mw.html.create('h2') | |||
head:wikitext(i18n.acquisition.header .. '[[File:Questionmark.png|right|24px|link=Path_of_Exile_Wiki:How_to_edit_item_acquisition]]') | |||
return tostring(head) .. table.concat(out) | |||
end | |||
-- | |||
-- Template:Item class | |||
-- | |||
function p.item_class (frame) | |||
-- Get args | |||
local tpl_args = getArgs(frame, { | |||
parentFirst = true | |||
}) | |||
frame = m_util.misc.get_frame(frame) | |||
if not doInfoCard then | |||
doInfoCard = require('Module:Infocard')._main | |||
end | |||
m_util.cast.factory.table('name', {key='full', tbl=m_game.constants.item.class})(tpl_args, frame) | |||
if tpl_args.name_list ~= nil then | |||
tpl_args.name_list = m_util.string.split(tpl_args.name_list, ',%s*') | |||
else | |||
tpl_args.name_list = {} | |||
end | end | ||
-- | |||
local ul = mw.html.create('ul') | |||
for _, item in ipairs(tpl_args.name_list) do | |||
ul | |||
:tag('li') | |||
:wikitext(item) | |||
:done() | |||
end | |||
-- Output Infocard | |||
local tplargs = { | |||
['header'] = tpl_args.name, | |||
['subheader'] = i18n.item_class_infobox.page .. i18n.item_class_infobox.info, | |||
[1] = i18n.item_class_infobox.also_referred_to_as .. tostring(ul), | |||
} | |||
-- cats | |||
local cats = { | |||
'Item classes', | |||
tpl_args.name, | |||
} | |||
-- Done | |||
return doInfoCard(tplargs) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug}) | |||
end | |||
-- ---------------------------------------------------------------------------- | |||
-- Debug stuff | |||
-- ---------------------------------------------------------------------------- | |||
p.debug = {} | |||
function p.debug._tbl_data(tbl) | |||
keys = {} | |||
for _, data in ipairs(core.result.generic_item) do | |||
if type(data.arg) == 'string' then | |||
keys[data.arg] = 1 | |||
elseif type(data.arg) == 'table' then | |||
for _, arg in ipairs(data.arg) do | |||
keys[arg] = 1 | |||
end | |||
end | end | ||
end | end | ||
local out = {} | |||
for key, _ in pairs(keys) do | |||
out[#out+1] = string.format("['%s'] = '1'", key) | |||
end | |||
return table.concat(out, ', ') | |||
end | |||
function p.debug.generic_item_all() | |||
return p.debug._tbl_data(core.result.generic_item) | |||
end | |||
function p.debug.skill_gem_all() | |||
return p.debug._tbl_data(core.result.skill_gem_new) | |||
return | |||
end | end | ||
return p | return p | ||
Revision as of 14:15, 2 January 2018
This module implements {{item link}} and facilitates the creation of item links.
The above documentation is transcluded from Module:Item link/doc.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
-- SMW reworked item module
-- ----------------------------------------------------------------------------
-- TODO
-- ----------------------------------------------------------------------------
-- Items
-- -----
-- Check if abyss jewels actually have radius modifiers
--
-- Has explicit mod ids (and others) changing order of values in the API.
--
-- Aggregate ids from Has granted skill id from modifiers
--
-- DROP restriction improvements:
-- drop monster type(s)
--
-- unique items:
-- 3D art
-- supporter attribution
-- option to hide explicit mods or override text
--
-- singular for weapon class in infoboxes
--
--
-- Maps:
-- Area level can be retrieved eventually
--
-- Essence:
-- type column
-- monster modifier info
-- ----------
-- Item table
-- ----------
-- Skills need proper range values in their tables
-- stat_column<i>_stat<j>_id -> make an lua pattern alternative
-- show drop area etc
--
-- ----------
-- Item class
-- ----------
--
-- remove the ul if name_list is not provided
-- maybe smw
-- -----------
-- rework todo
-- -----------
-- check unique map properties copy
-- REMOVED:
-- is_essence
-- RENAMED
-- essence_tier -> essence_level
-- release_version
-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------
local xtable = require('Module:Table')
local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local m_skill = require('Module:Skill')
local m_area = require('Module:Area')
local f_item_link = require('Module:Item link').item_link
local cargo = mw.ext.cargo
local p = {}
local c = {}
c.image_size = 39
c.image_size_full = c.image_size * 2
-- ----------------------------------------------------------------------------
-- Temporary fixes
-- ----------------------------------------------------------------------------
-- Move to util
function m_util.cast.factory.array_table(k, args)
-- Arguments:
-- tbl - table to check against
-- errmsg - error message if no element was found; should accept 1 parameter
args = args or {}
return function (tpl_args, frame)
local elements
if tpl_args[k] ~= nil then
elements = m_util.string.split(tpl_args[k], ',%s*')
for _, element in ipairs(elements) do
local r = m_util.table.find_in_nested_array{value=element, tbl=args.tbl, key='full'}
if r == nil then
error(m_util.html.error{msg=string.format(args.errmsg or i18n.errors.missing_element, element)})
end
end
tpl_args[args.key_out or k] = xtable:new(elements)
end
end
end
function m_util.cast.factory.assoc_table(k, args)
-- Arguments:
--
-- tbl
-- errmsg
-- key_out
return function (tpl_args, frame)
local elements
if tpl_args[k] ~= nil then
elements = m_util.string.split(tpl_args[k], ',%s*')
for _, element in ipairs(elements) do
if args.tbl[element] == nil then
error(m_util.html.error{msg=string.format(args.errmsg or i18n.errors.missing_element, element)})
end
end
tpl_args[args.key_out or k] = elements
end
end
end
function m_util.cargo.query(tables, fields, query, args)
-- Wrapper for mw.ext.cargo.query that helps to work around some bugs
--
-- Current workarounds:
-- field names will be "aliased" to themselves
--
-- Takes 3 arguments:
-- tables - array containing tables
-- fields - array containing fields; these will automatically be renamed to the way they are specified to work around bugs when results are returned
-- query - array containing cargo sql clauses
-- args
-- args.keep_empty
-- Cargo bug workaround
args = args or {}
for i, field in ipairs(fields) do
-- already has some alternate name set, so do not do this.
if string.find(field, '=') == nil then
fields[i] = string.format('%s=%s', field, field)
end
end
local results = cargo.query(table.concat(tables, ','), table.concat(fields, ','), query)
if args.keep_empty == nil then
for _, row in ipairs(results) do
for k, v in pairs(row) do
if v == "" then
row[k] = nil
end
end
end
end
return results
end
function m_util.cargo.array_query(args)
-- Performs a long "OR" query from the given array and field validating that there is only exactly one match returned
--
-- args:
-- tables - array of tables (see util.cargo.query)
-- fields - array of fields (see util.cargo.query)
-- query - array containing cargo sql clauses [optional] (see util.cargo.query)
-- id_array - list of ids to query for
-- id_field - name of the id field, will be automatically added to fields
--
-- RETURN:
-- table - results as given by mw.ext.cargo.query
--
args.query = args.query or {}
args.fields[#args.fields+1] = args.id_field
local id_array = {}
for i, id in ipairs(args.id_array) do
id_array[i] = string.format('%s="%s"', args.id_field, id)
end
if args.query.where then
args.query.where = string.format('(%s) AND (%s)', args.query.where, table.concat(id_array, ' OR '))
else
args.query.where = table.concat(id_array, ' OR ')
end
--
-- Check for duplicates
--
-- The usage of distinct should elimate duplicates here from cargo being bugged while still showing actual data duplicates.
local results = m_util.cargo.query(
args.tables,
{
string.format('COUNT(DISTINCT %s._pageID)=count', args.tables[1]),
args.id_field,
},
{
join=args.query.join,
where=args.query.where,
groupBy=args.id_field,
having=string.format('COUNT(DISTINCT %s._pageID) > 1', args.tables[1]),
}
)
if #results > 0 then
out = {}
for _, row in ipairs(results) do
out[#out+1] = string.format('%s (%s pages found)', row[args.id_field], row['count'])
end
error(string.format('Found duplicates for field "%s":\n %s', args.id_field, table.concat(out, '\n')))
end
--
-- Prepare query
--
if args.query.groupBy then
args.query.groupBy = string.format('%s._pageID,%s', args.tables[1], args.query.groupBy)
else
args.query.groupBy = string.format('%s._pageID', args.tables[1])
end
local results = m_util.cargo.query(
args.tables,
args.fields,
args.query
)
--
-- Check missing results
--
if #results ~= #args.id_array then
local missing = {}
for _, id in ipairs(args.id_array) do
missing[id] = true
end
for _, row in ipairs(results) do
missing[row[args.id_field]] = nil
end
local missing_ids = {}
for k, _ in pairs(missing) do
missing_ids[#missing_ids+1] = k
end
error(string.format('Missing results for "%s" field with values: \n%s', args.id_field, table.concat(missing_ids, '\n')))
end
return results
end
-- ----------------------------------------------------------------------------
-- Strings
-- ----------------------------------------------------------------------------
-- This section contains strings used by this module.
-- Add new strings here instead of in-code directly, this will help other
-- people to correct spelling mistakes easier and help with translation to
-- other PoE wikis.
--
-- TODO: Maybe move this out to a separate sub-page module
local i18n = {
range = '(%s to %s)',
inventory_icon = 'File:%s inventory icon.png',
status_icon = 'File:%s status icon.png',
skill_screenshot = 'File:%s skill screenshot.jpg',
divination_card_art = 'File:%s card art.png',
gem_tag_category = '[[:Category:%s (gem tag)|%s]]',
categories = {
-- maintenance cats
improper_modifiers = 'Items with improper modifiers',
missing_release_version = 'Items without a release version',
broken_upgraded_from_reference = 'Items with broken item references in upgraded from parameters',
-- regular cats
alternate_artwork = 'Items with alternate artwork',
-- misc
gem_tag_affix = '%s (gem tag)',
unique_affix = 'Unique %s',
prophecies = 'Prophecies',
talismans = 'Talismans',
essences = 'Essences',
},
stat_skip_patterns = {
maps = {
'%d+%% increased Quantity of Items found in this Area',
'%d+%% increased Rarity of Items found in this Area',
'%+%d+%% Monster pack size',
-- ranges
'%(%d+%-%d+%)%% increased Quantity of Items found in this Area',
'%(%d+%-%d+%)%% increased Rarity of Items found in this Area',
'%+%(%d+%-%d+%)%% Monster pack size',
},
jewels = {
'Limited to %d+ %(Hidden%)',
'Jewel has a radius of %d+ %(Hidden%)',
},
},
help_text_defaults = {
active_gem = 'Place into an item socket of the right colour to gain this skill. Right click to remove from a socket.',
support_gem = 'This is a Support Gem. It does not grant a bonus to your character, but skills in sockets connected to it. Place into an item socket connected to a socket containing the Active Skill Gem you wish to augment. Right click to remove from a socket.',
hideout_doodad = 'Right click on this item then left click on a location on the ground to create the object.',
jewel = 'Place into an allocated Jewel Socket on the Passive Skill Tree. Right click to remove from the Socket.',
},
-- Used by the item table
item_table = {
item = 'Item',
skill_gem = 'Skill gem',
physical_dps = m_util.html.abbr('pDPS', 'physical damage per second'),
fire_dps = m_util.html.abbr('Fire DPS', 'fire damage per second'),
cold_dps = m_util.html.abbr('Cold DPS', 'cold damage per second'),
lightning_dps = m_util.html.abbr('Light. DPS', 'lightning damage per second'),
chaos_dps = m_util.html.abbr('Chaos DPS', 'chaos damage per second'),
elemental_dps = m_util.html.abbr('eDPS', 'elemental damage (i.e. fire/cold/lightning) per second'),
poison_dps = m_util.html.abbr('Poison DPS', 'poison damage (i.e. physical/chaos) per second'),
dps = m_util.html.abbr('DPS', 'total damage (i.e. physical/fire/cold/lightning/chaos) per second'),
base_item = 'Base Item',
item_class = 'Item Class',
essence_level = 'Essence<br>Level',
drop_level = 'Drop<br>Level',
drop_leagues = 'Drop Leagues',
drop_areas = 'Drop Areas',
drop_text = 'Additional<br>Drop Restrictions',
stack_size = 'Stack<br>Size',
stack_size_currency_tab = m_util.html.abbr('Tab<br>Stack<br>Size', 'Stack size in the currency stash tab'),
armour = m_util.html.abbr('AR', 'Armour'),
evasion = m_util.html.abbr('EV', 'Evasion Rating'),
energy_shield = m_util.html.abbr('ES', 'Energy Shield'),
block = m_util.html.abbr('Block', 'Chance to Block'),
damage = m_util.html.abbr('Damage', 'Colour coded damage'),
attacks_per_second = m_util.html.abbr('APS', 'Attacks per second'),
local_critical_strike_chance = m_util.html.abbr('Crit', 'Local weapon critical strike chance'),
flask_life = m_util.html.abbr('Life', 'Life regenerated over the flask duration'),
flask_mana = m_util.html.abbr('Mana', 'Mana regenerated over the flask duration'),
flask_duration = 'Duration',
flask_charges_per_use = m_util.html.abbr('Usage', 'Number of charges consumed on use'),
flask_maximum_charges = m_util.html.abbr('Capacity', 'Maximum number of flask charges held'),
item_limit = 'Limit',
jewel_radius = 'Radius',
map_tier = 'Map<br>Tier',
map_level = 'Map<br>Level',
map_guild_character = m_util.html.abbr('Char', 'Character for the guild tag'),
buff_effects = 'Buff Effects',
stats = 'Stats',
effects = 'Effect(s)',
flavour_text = 'Flavour Text',
prediction_text = 'Prediction',
help_text = 'Help Text',
seal_cost = m_util.html.abbr('Seal<br>Cost', 'Silver Coin cost of sealing this prophecies into an item'),
objective = 'Objective',
reward = 'Reward',
buff_icon = 'Buff<br>Icon',
-- Skills
support_gem_letter = m_util.html.abbr('L', 'Support gem letter.'),
skill_icon = 'Icon',
description = 'Description',
skill_critical_strike_chance = m_util.html.abbr('Crit', 'Critical Strike Chance'),
cast_time = m_util.html.abbr('Cast<br>Time', 'Casting time of the skill in seconds'),
damage_effectiveness = m_util.html.abbr('Dmg.<br>Eff.', 'Damage Effectiveness'),
mana_cost_multiplier = m_util.html.abbr('MCM', 'Mana cost multiplier - missing values indicate it changes on gem level'),
mana_cost = m_util.html.abbr('Mana', 'Mana cost'),
reserves_mana_suffix = m_util.html.abbr('R', 'reserves mana'),
vaal_souls_requirement = m_util.html.abbr('Souls', 'Vaal souls requirement in Normal/Cruel/Merciless difficulty'),
stored_uses = m_util.html.abbr('Uses', 'Maximum number of stored uses'),
primary_radius = m_util.html.abbr('R1', 'Primary radius'),
secondary_radius = m_util.html.abbr('R2', 'Secondary radius'),
tertiary_radius = m_util.html.abbr('R3', 'Tertiary radius'),
},
-- Used by the item info box
tooltips = {
corrupted = 'Corrupted',
support_icon = 'Icon: %s',
radius = 'Radius: %s',
mana_reserved = 'Mana Reserved: %s',
mana_cost = 'Mana Cost: %s',
mana_multiplier = 'Mana Multiplier: %s',
vaal_souls_per_use = 'Souls per use: %s',
stored_uses = 'Can store %s use(s)',
cooldown_time = 'Cooldown Time: %s',
cast_time = 'Cast Time: %s',
critical_strike_chance = 'Critical Strike Chance: %s',
damage_effectiveness = 'Damage Effectiveness: %s',
projectile_speed = 'Projectile Speed: %s',
quality = 'Quality: %s',
physical_damage = 'Physical Damage: %s',
elemental_damage = 'Elemental Damage:%s',
chaos_damage = 'Chaos Damage: %s',
attacks_per_second = 'Attacks per Second: %s',
weapon_range = 'Weapon Range: %s',
map_level = 'Map Level: %s',
map_tier = 'Map Tier: %s',
map_guild_character = m_util.html.abbr('Guild Character', 'When used in guild creation, this map can be used for the listed character') .. ': %s',
item_quantity = 'Item Quantity: %s',
item_rarity = 'Item Rarity: %s',
monster_pack_size = 'Monster Pack Size: %s',
limited_to = 'Limited to: %s',
flask_mana_recovery = 'Recovers %s Mana over %s seconds',
flask_life_recovery = 'Recovers %s Life over %s seconds',
flask_duration = 'Lasts %s Seconds',
flask_charges_per_use = 'Consumes %s of %s Charges on use',
chance_to_block = 'Chance to Block: %s',
armour = 'Armour: %s',
evasion = 'Evasion: %s',
energy_shield = 'Energy Shield: %s',
talisman_tier = 'Talisman Tier: %s',
stack_size = 'Stack Size: %s',
essence_level = 'Essence Level: %s',
requires = 'Requires %s',
level_inline = 'Level %s',
level = 'Level: %s',
gem_quality = 'Per 1% Quality:',
variation_singular = 'Variation',
variation_plural = 'Variations',
favour_cost = 'Favour cost: %s',
seal_cost = 'Seal cost: <br>%s',
cannot_be_traded_or_modified = 'Cannot be traded or modified',
-- secondary infobox
drop_restrictions = 'Acquisition',
league_restriction = m_util.html.abbr('League(s):', 'Item can be obtained in relation to these league(s)') .. ' %s',
drop_disabled = 'DROP DISABLED',
purchase_costs = m_util.html.abbr('Purchase Costs', 'Cost of purchasing an item of this type at NPC vendors. This does not indicate whether NPCs actually sell the item.'),
sell_price = m_util.html.abbr('Sell Price', 'Items or currency received when selling this item at NPC vendors. Certain vendor recipes may override this value.'),
damage_per_second = 'Weapon DPS',
physical_dps = 'Physical',
fire_dps = 'Fire',
cold_dps = 'Cold',
lightning_dps = 'Lightning',
chaos_dps = 'Chaos',
elemental_dps = 'Elemental',
poison_dps = 'Phys+Chaos',
dps = 'Total',
},
acquisition = {
header = 'Item acquisition',
area = 'This item can be acquired in the following areas:',
upgraded_from = 'This item can be acquired through the following upgrade paths or vendor recipes:',
ingredient_header = 'Usage in recipes',
ingredient = 'This item is used by upgrade paths or vendor recipes to create the following items:',
},
item_class_infobox = {
page = '[[Item class]]',
info = m_util.html.abbr('(?)', 'Item classes categorize items. Classes are often used to restrict items or skill gems to a specific class or by item filters'),
also_referred_to_as = 'Also referred to as:',
},
debug = {
base_item_field_not_found = 'Base item property not found: %s.%s',
field_value_mismatch = 'Value for argument "%s" is set to something else then default: %s',
},
errors = {
missing_base_item = 'Rarity is set to above normal, but base item is not set. A base item for rarities above normal is required!',
missing_rarity = 'Base item parameter is set, but rarity is set to normal. A rarity above normal is required!',
missing_amount = 'Item amount is missing or not a number (%s)',
upgraded_from_broken_reference = 'Item reference in %s is broken (results: %s)',
duplicate_base_items = 'More then one result found for the specified base item. Consider using base_item_page or base_item_id to narrow down the results.',
invalid_league = '%s is not a recognized league',
invalid_tag = '%s is not a valid tag',
generic_argument_parameter = 'Unrecognized %s parameter "%s"',
invalid_item_class = 'Invalid item class',
invalid_item_table_mode = 'Invalid mode for item table',
non_unique_relic = 'Only unique items can be be relics',
},
}
-- ----------------------------------------------------------------------------
-- Other stuff
-- ----------------------------------------------------------------------------
local h = {}
function h.debug(tpl_args, func)
if tpl_args.debug == nil then
return
end
func()
end
function h.na_or_val(tr, value, func)
if value == nil then
tr:wikitext(m_util.html.td.na())
else
local raw_value = value
if func ~= nil then
value = func(value)
end
tr
:tag('td')
:attr('data-sort-value', raw_value)
:wikitext(value)
:done()
end
end
-- helper to loop over the range variables easier
h.range_map = {
min = {
var = '_range_minimum',
},
max = {
var = '_range_maximum',
},
avg = {
var = '_range_average',
},
}
h.range_fields = {
{
field = '_range_minimum',
type = 'Integer',
},
{
field = '_range_maximum',
type = 'Integer',
},
{
field = '_range_average',
type = 'Integer',
},
{
field = '_range_text',
type = 'Text',
},
{
field = '_range_colour',
type = 'String',
},
{
field = '_html',
type = 'Text',
},
}
function h.handle_range_args(tpl_args, frame, argument_key, field, value, fmt_options)
fmt_options = mw.clone(fmt_options)
fmt_options.return_color = true
local html, colour = h.format_value(tpl_args, frame, value, fmt_options)
tpl_args[argument_key .. '_html'] = html
tpl_args[field .. '_html'] = html
tpl_args[field .. '_range_colour'] = colour
fmt_options = mw.clone(fmt_options)
fmt_options.no_color = true
tpl_args[field .. '_range_text'] = h.format_value(tpl_args, frame, value, fmt_options)
end
function h.stats_update(tpl_args, id, value, modid, key)
if tpl_args[key][id] == nil then
tpl_args[key][id] = {
references = {modid},
min = value.min,
max = value.max,
avg = value.avg,
}
else
if modid ~= nil then
table.insert(tpl_args[key][id].references, modid)
end
tpl_args[key][id].min = tpl_args[key][id].min + value.min
tpl_args[key][id].max = tpl_args[key][id].max + value.max
tpl_args[key][id].avg = tpl_args[key][id].avg + value.avg
end
end
h.stat = {}
function h.stat.add (value, stat_cached)
value.min = value.min + stat_cached.min
value.max = value.max + stat_cached.max
end
function h.stat.more (value, stat_cached)
value.min = value.min * (1 + stat_cached.min / 100)
value.max = value.max * (1 + stat_cached.max / 100)
end
function h.stat.more_inverse (value, stat_cached)
value.min = value.min / (1 + stat_cached.min / 100)
value.max = value.max / (1 + stat_cached.max / 100)
end
h.tbl = {}
function h.tbl.range_fields(field)
return function()
local fields = {}
for _, partial_field in ipairs({'maximum', 'text', 'colour'}) do
fields[#fields+1] = string.format('%s_range_%s', field, partial_field)
end
return fields
end
end
h.tbl.display = {}
function h.tbl.display.na_or_val(tr, value, data)
return h.na_or_val(tr, value)
end
function h.tbl.display.seconds(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('%ss', value)
end)
end
function h.tbl.display.percent(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('%s%%', value)
end)
end
function h.tbl.display.wikilink(tr, value, data)
return h.na_or_val(tr, value, function(value)
return string.format('[[%s]]', value)
end)
end
h.tbl.display.factory = {}
function h.tbl.display.factory.value(args)
args.options = args.options or {}
return function(tr, data, properties)
values = {}
for index, prop in ipairs(properties) do
local value = data[prop]
if args.options[index] and args.options[index].fmt then
value = string.format(args.options[index].fmt, value)
end
values[#values+1] = value
end
local td = tr:tag('td')
td:attr('data-sort-value', table.concat(values, ', '))
td:wikitext(table.concat(values, ', '))
if args.colour then
td:attr('class', 'tc -' .. args.colour)
end
end
end
function h.tbl.display.factory.range(args)
-- args: table
-- property
return function (tr, data, fields)
tr
:tag('td')
:attr('data-sort-value', data[string.format('%s_range_maximum', args.field)] or '0')
:attr('class', 'tc -' .. (data[string.format('%s_range_colour', args.field)] or 'default'))
:wikitext(data[string.format('%s_range_text', args.field)])
:done()
end
end
function h.format_value(tpl_args, frame, value, options)
-- value: table
-- min:
-- max:
-- options: table
-- fmt: formatter to use for the value instead of valfmt
-- fmt_range: formatter to use for the range values. Default: (%s to %s)
-- inline: Use this format string to insert value
-- inline_color: colour to use for the inline value; false to disable colour
-- func: Function to adjust the value with before output
-- color: colour code for m_util.html.poe_color, overrides mod colour
-- no_color: set to true to ingore colour entirely
-- return_color: also return colour
if options.no_color == nil then
if options.color then
value.color = options.color
elseif value.base ~= value.min or value.base ~= value.max then
value.color = 'mod'
else
value.color = 'value'
end
end
if options.func ~= nil then
value.min = options.func(tpl_args, frame, value.min)
value.max = options.func(tpl_args, frame, value.max)
end
if options.fmt == nil then
options.fmt = '%s'
elseif type(options.fmt) == 'function' then
options.fmt = options.fmt(tpl_args, frame)
end
if value.min == value.max then
value.out = string.format(options.fmt, value.min)
else
value.out = string.format(string.format(options.fmt_range or i18n.range, options.fmt, options.fmt), value.min, value.max)
end
if options.no_color == nil then
value.out = m_util.html.poe_color(value.color, value.out)
end
local return_color
if options.return_color ~= nil then
return_color = value.color
end
local text = options.inline
if type(text) == 'string' then
elseif type(text) == 'function' then
text = text(tpl_args, frame)
else
text = nil
end
if text and text ~= '' then
local color
if options.inline_color == nil then
color = 'default'
elseif options.inline_color ~= false then
color = color.inline_color
end
if color ~= nil then
text = m_util.html.poe_color(color, text)
end
return string.format(text, value.out), return_color
end
-- If we didn't return before, return here
return value.out, return_color
end
-- ----------------------------------------------------------------------------
-- core
-- ----------------------------------------------------------------------------
local core = {}
function core.build_cargo_data(tpl_args, frame)
for _, table_name in ipairs(core.item_classes[tpl_args.class].tables) do
for k, field_data in pairs(core.cargo[table_name].fields) do
field_data.table = table_name
for _, stat_data in pairs(core.stat_map) do
if stat_data == k then
for _, range_field in ipairs(h.range_fields) do
local field_name = stat_data.field .. range_field.field
local data = {
no_copy = true,
table = table_name,
field = field_name,
type = range_field.type,
}
core.cargo[table_name].fields[field_name] = data
core.map[field_name] = data
end
break
end
end
if table_name == 'weapons' then
for _, dps_data in ipairs(core.dps_map) do
for _, range_field in ipairs(h.range_fields) do
local field_name = dps_data.field .. range_field.field
local data = {
no_copy = true,
table = table_name,
field = field_name,
type = range_field.type,
}
core.cargo[table_name].fields[field_name] = data
core.map[field_name] = data
end
end
end
end
end
end
function core.validate_mod(tpl_args, frame, args)
-- args:
-- key - implict or explicit
-- i
-- value
local value = tpl_args[args.key .. args.i]
local out = {
result=nil,
modid=nil,
type=args.key,
text=nil,
}
if value ~= nil then
table.insert(tpl_args.mods, value)
table.insert(tpl_args[args.key .. '_mods'], value)
out.modid = value
out.text = tpl_args[args.key .. args.i .. '_text']
--out.result = nil
table.insert(tpl_args._mods, out)
return true
else
value = tpl_args[args.key .. args.i .. '_text']
if value ~= nil then
tpl_args._flags.text_modifier = true
out.result = value
table.insert(tpl_args._mods, out)
return true
end
end
return false
end
function core.process_smw_mods(tpl_args, frame)
tpl_args.sell_prices = {}
if #tpl_args.mods > 0 then
local mods = {}
local mod_ids = {}
for _, mod_data in ipairs(tpl_args._mods) do
if mod_data.result == nil then
mods[mod_data.modid] = mod_data
mod_ids[#mod_ids+1] = mod_data.modid
mods[#mods+1] = string.format('mods.id="%s"', mod_data.modid)
end
end
local results = m_util.cargo.array_query{
tables={'mods'},
fields={'mods._pageName', 'mods.id', 'mods.required_level', 'mods.stat_text'},
id_field='mods.id',
id_array=mod_ids,
}
for _, data in ipairs(results) do
local mod_data = mods[data['mods.id']]
mod_data.result = data
-- update item level requirement
local keys = {'required_level_final'}
-- only update base item requirement if this is an implicit
if mod_data.key == 'implicit' then
keys[#keys+1] = 'required_level'
end
for _, key in ipairs(keys) do
local req = math.floor(tonumber(data['mods.required_level']) * 0.8)
if req > tpl_args[key] then
tpl_args[key] = req
end
end
end
-- fetch stats
results = cargo.query(
'mods,mod_stats',
-- Workaround: Cargo messing up the ids here
'mods.id=mods.id, mod_stats.id=mod_stats.id, mod_stats.min, mod_stats.max',
{
join='mods._pageID=mod_stats._pageID',
where='mod_stats.id IS NOT NULL AND (' .. table.concat(mods, ' OR ') .. ')',
-- Workaround: Fix cargo duplicates
groupBy='mod_stats._pageID, mod_stats.id',
}
)
for _, data in ipairs(results) do
-- Stat subobject
local mod_data = mods[data['mods.id']]
if mod_data.result.stats == nil then
mod_data.result.stats = {data, }
else
mod_data.result.stats[#mod_data.result.stats+1] = data
end
local id = data['mod_stats.id']
local value = {
min = tonumber(data['mod_stats.min']),
max = tonumber(data['mod_stats.max']),
}
value.avg = (value.min+value.max)/2
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], '_stats')
if mod_data.type ~= 'implicit' then
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], '_explicit_stats')
else
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], '_implicit_stats')
end
end
-- fetch sell prices
results = cargo.query(
'mods,mod_sell_prices',
'mods.id, mod_sell_prices.amount, mod_sell_prices.name',
{
join='mods._pageID=mod_sell_prices._pageID',
where='mod_sell_prices.amount IS NOT NULL AND (' .. table.concat(mods, ' OR ') .. ')',
-- Workaround: Fix cargo duplicates
groupBy='mod_sell_prices._pageID, mod_sell_prices.name',
}
)
for _, data in ipairs(results) do
local mod_data = mods[data['mods.id']]
if mod_data.type ~= implicit then
local values = {
name = data['mod_sell_prices.name'],
amount = tonumber(data['mod_sell_prices.amount']),
}
tpl_args.sell_prices[values.name] = (tpl_args.sell_prices[values.name] or 0) + values.amount
end
end
end
local missing_sell_price = true
for _, _ in pairs(tpl_args.sell_prices) do
missing_sell_price = false
break
end
if missing_sell_price then
tpl_args.sell_prices['Scroll Fragment'] = 1
end
-- Set sell price on page
tpl_args.sell_price_order = {}
for name, amount in pairs(tpl_args.sell_prices) do
tpl_args.sell_price_order[#tpl_args.sell_price_order+1] = name
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_sell_prices',
amount = amount,
item_name = name,
}
end
table.sort(tpl_args.sell_price_order)
end
function core.process_base_item(tpl_args, frame, args)
local where
if tpl_args.base_item_id ~= nil then
where = string.format('items.metadata_id="%s"', tpl_args.base_item_id)
elseif tpl_args.base_item_page ~= nil then
where = string.format('items._pageName="%s"' , tpl_args.base_item_page)
elseif tpl_args.base_item ~= nil then
where = string.format('items.name="%s"' , tpl_args.base_item)
elseif tpl_args.rarity ~= 'Normal' then
error(m_util.html.error{msg=i18n.errors.missing_base_item})
else
return
end
if where ~= nil and tpl_args.rarity == 'Normal' then
error(m_util.html.error{msg=i18n.errors.missing_rarity})
end
where = string.format('%s AND items.class="%s" AND items.rarity="Normal"', where, tpl_args.class)
local join = {}
for _, table_name in ipairs(core.item_classes[tpl_args.class].tables) do
if table_name ~= 'items' then
join[#join+1] = string.format('items._pageID=%s._pageID', table_name)
end
end
local fields = {
'items._pageName=items._pageName',
'items.name=items.name',
'items.metadata_id=items.metadata_id',
}
for _, k in ipairs(tpl_args._base_item_args) do
local data = core.map[k]
if data.field ~= nil then
fields[#fields+1] = string.format('%s.%s=%s.%s', data.table, data.field, data.table, data.field)
end
end
local result = cargo.query(
table.concat(core.item_classes[tpl_args.class].tables, ','),
table.concat(fields, ','),
{
where=where,
join=table.concat(join, ','),
-- Workaround: Fix cargo duplicates
groupBy='items._pageID',
}
)
if #result > 1 then
error(m_util.html.error{msg=i18n.errors.duplicate_base_items})
-- TODO be more explicit in the error?
end
result = result[1]
--error(mw.dumpObject(result))
tpl_args.base_item_data = result
core.process_arguments(tpl_args, frame, {array={'base_item', 'base_item_page', 'base_item_id'}})
--Copy values..
for _, k in ipairs(tpl_args._base_item_args) do
local data = core.map[k]
if data.field ~= nil and data.func_fetch == nil then
local value = result[string.format('%s.%s', data.table, data.field)]
-- I can just use data.default since it will be nil if not provided (nil == nil). Neat! ;)
if value ~= "" and (tpl_args[k] == data.default or type(data.default) == 'function') then
tpl_args[k] = value
if data.func ~= nil then
data.func(tpl_args, frame)
end
if data.func_copy ~= nil then
data.func_copy(tpl_args, frame)
end
elseif value == "" then
h.debug(tpl_args, function ()
mw.logObject(string.format(i18n.debug.base_item_field_not_found, data.table, data.field))
end)
elseif tpl_args[k] ~= data.default then
h.debug(tpl_args, function ()
mw.logObject(string.format(i18n.debug.field_value_mismatch, k, tostring(tpl_args[k])))
end)
end
elseif data.func_fetch ~= nil then
data.func_fetch(tpl_args, frame)
end
end
end
function core.process_arguments(tpl_args, frame, args)
for _, k in ipairs(args.array) do
local data = core.map[k]
if data == nil then
error(string.format('Invalid key or missing data for "%s"', k))
end
if data.no_copy == nil then
table.insert(tpl_args._base_item_args, k)
end
if data.func ~= nil then
data.func(tpl_args, frame)
--[[local status, err = pcall(data.func)
-- an error was raised, return the error string instead of the template
if not status then
return err
end
]]--
end
if tpl_args[k] == nil then
if tpl_args.class and core.item_classes[tpl_args.class].defaults ~= nil and core.item_classes[tpl_args.class].defaults[k] ~= nil then
tpl_args[k] = core.item_classes[tpl_args.class].defaults[k]
elseif data.default ~= nil then
if type(data.default) == 'function' then
tpl_args[k] = data.default()
else
tpl_args[k] = data.default
end
end
end
end
end
function core.process_mod_stats(tpl_args, args)
local lines = {}
local skip = core.class_specifics[tpl_args.class]
if skip then
skip = skip.skip_stat_lines
end
for _, modinfo in ipairs(tpl_args._mods) do
if modinfo.type == args.type then
if modinfo.modid == nil then
table.insert(lines, modinfo.result)
-- Allows the override of the SMW fetched mod texts for this modifier via <modtype><id>_text parameter
elseif modinfo.text ~= nil then
if m_util.cast.boolean(modinfo.text) then
table.insert(lines, modinfo.text)
end
else
for _, line in ipairs(m_util.string.split(modinfo.result['mods.stat_text'], '<br>')) do
if line ~= '' then
if skip == nil then
table.insert(lines, line)
else
local skipped = false
for _, pattern in ipairs(skip) do
if string.match(line, pattern) then
skipped = true
break
end
end
if not skipped then
table.insert(lines, line)
end
end
end
end
end
end
end
if #lines == 0 then
return
else
return table.concat(lines, '<br>')
end
end
function core.process_upgraded_from(tpl_args, frame)
local sets = {}
local setid = 1
local set
repeat
local prefix = string.format('upgraded_from_set%s_', setid)
local groupid = 1
local group
set = {
groups = {},
optional = m_util.cast.boolean(tpl_args[prefix .. 'optional']),
text = tpl_args[prefix .. 'text'],
}
repeat
local group_prefix = string.format('%sgroup%s_', prefix, groupid)
group = {
item_name = tpl_args[group_prefix .. 'item_name'],
item_id = tpl_args[group_prefix .. 'item_id'],
item_page = tpl_args[group_prefix .. 'item_page'],
amount = tonumber(tpl_args[group_prefix .. 'amount']),
notes = tpl_args[group_prefix .. 'notes'],
}
if group.item_name ~= nil or group.item_id ~= nil or group.item_page ~= nil then
if group.amount == nil then
error(string.format(i18n.errors.missing_amount, group_prefix .. 'amount'))
else
-- for verification purposes
local where
if group.item_id then
where = string.format('items.metadata_id="%s"', group.item_id)
elseif group.item_page then
where = string.format('items._pageName="%s"', group.item_page)
elseif group.item_name then
where = string.format('items.name="%s"', group.item_name)
end
local results = cargo.query(
'items',
'items._pageName, items.name, items.metadata_id',
{
where=where,
-- Workaround: Fix cargo duplicates
groupBy='items._pageID',
}
)
if #results ~= 1 then
tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.upgraded_from_broken_reference, string.sub(group_prefix, 1, -2), #results)
tpl_args._flags.broken_upgraded_from_reference = true
else
results = results[1]
if results['items.metadata_id'] ~= '' then
group.item_id = results['items.metadata_id']
end
group.item_name = results['items.name']
group.page = results['items._pageName']
set.groups[#set.groups+1] = group
end
end
end
groupid = groupid + 1
until group.item_name == nil and group.item_id == nil and group.item_page == nil
-- set was empty, can terminate safely
if #set.groups == 0 then
set = nil
else
setid = setid + 1
sets[#sets+1] = set
end
until set == nil
if #sets == 0 then
return
end
tpl_args.upgrade_from_sets = sets
-- set upgraded_from data
for i, set in ipairs(sets) do
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'upgraded_from_sets',
set_id = i,
text = set.text,
--'optional' = set.optional,
}
for j, group in ipairs(set.groups) do
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'upgraded_from_groups',
group_id = j,
set_id = i,
item_name = group.item_name,
item_id = group.item_id,
item_page = group.page,
amount = group.amount,
notes = group.notes,
}
end
end
end
--
-- function factory
--
core.factory = {}
function core.factory.display_value(args)
-- args:
-- type: Type of the keys (nil = regular, gem = skill gems, stat = stats)
-- options<Array>:
-- key: key to use
-- allow_zero: allow zero values
-- hide_default: hide the value if this is set
-- hide_default_key: key to use if it isn't equal to the key parameter
-- -- from h.format_value --
-- fmt: formatter to use for the value instead of valfmt
-- fmt_range: formatter to use for the range values. Default: (%s to %s)
-- insert: insert results into this object
-- func: Function to adjust the value with before output
-- color: colour code for m_util.html.poe_color, overrides mod colour
-- no_color: set to true to ingore colour entirely
for k, default in pairs({options = {}}) do
if args[k] == nil then
args[k] = default
end
end
return function (tpl_args, frame)
local base_values = {}
local temp_values = {}
if args.type == 'gem' then
if not core.class_groups.gems.keys[tpl_args.class] then
return
end
for i, data in ipairs(args.options) do
local value = tpl_args.skill_levels[0][data.key]
if value ~= nil then
base_values[#base_values+1] = value
temp_values[#temp_values+1] = {value={min=value,max=value}, index=i}
else
value = {
min=tpl_args.skill_levels[1][data.key],
max=tpl_args.skill_levels[tpl_args.max_level][data.key],
}
if value.min == nil or value.max == nil then
else
base_values[#base_values+1] = value.min
temp_values[#temp_values+1] = {value=value, index=i}
end
end
end
elseif args.type == 'stat' then
for i, data in ipairs(args.options) do
local value = tpl_args._stats[data.key]
if value ~= nil then
base_values[i] = value.min
temp_values[#temp_values+1] = {value=value, index=i}
end
end
else
for i, data in ipairs(args.options) do
base_values[i] = tpl_args[data.key]
local value = {}
if tpl_args[data.key .. '_range_minimum'] ~= nil then
value.min = tpl_args[data.key .. '_range_minimum']
value.max = tpl_args[data.key .. '_range_maximum']
elseif tpl_args[data.key] ~= nil then
value.min = tpl_args[data.key]
value.max = tpl_args[data.key]
end
if value.min == nil then
else
temp_values[#temp_values+1] = {value=value, index=i}
end
end
end
local final_values = {}
for i, data in ipairs(temp_values) do
local opt = args.options[data.index]
local insert = false
if opt.hide_default == nil then
insert = true
elseif opt.hide_default_key == nil then
local v = data.value
if opt.hide_default ~= v.min and opt.hide_default ~= v.max then
insert = true
end
else
local v = {
min = tpl_args[opt.hide_default_key .. '_range_minimum'],
max = tpl_args[opt.hide_default_key .. '_range_maximum'],
}
if v.min == nil or v.max == nil then
if opt.hide_default ~= tpl_args[opt.hide_default_key] then
insert = true
end
elseif opt.hide_default ~= v.min and opt.hide_default ~= v.max then
insert = true
end
end
if insert == true then
table.insert(final_values, data)
end
end
-- all zeros = dont display and return early
if #final_values == 0 then
return nil
end
local out = {}
for i, data in ipairs(final_values) do
local value = data.value
value.base = base_values[data.index]
local options = args.options[data.index]
if options.color == nil and args.type == 'gem' then
value.color = 'value'
end
out[#out+1] = h.format_value(tpl_args, frame, value, options)
end
if args.inline then
return m_util.html.poe_color('default', string.format(args.inline, unpack(out)))
else
return table.concat(out, '')
end
end
end
function core.factory.display_value_only(key)
return function(tpl_args, frame)
return tpl_args[key]
end
end
function core.factory.descriptor_value(args)
-- Arguments:
-- key
-- tbl
args = args or {}
return function (tpl_args, frame, value)
args.tbl = args.tbl or tpl_args
if args.tbl[args.key] then
value = m_util.html.abbr(value, args.tbl[args.key])
end
return value
end
end
function core.factory.damage_html(args)
return function(tpl_args, frame)
if args.key ~= 'physical' then
args.color = args.key
args.no_color = true
end
local keys = {
min=args.key .. '_damage_min',
max=args.key .. '_damage_max',
}
local value = {}
for ktype, key in pairs(keys) do
value[ktype] = core.factory.display_value{options={ [1] = {
key = key,
no_color = args.no_color,
hide_default = 0
}}}(tpl_args, frame)
end
if value.min and value.max then
value = value.min .. '–' .. value.max
if args.color ~= nil then
value = m_util.html.poe_color(args.color, value)
end
tpl_args[args.key .. '_damage_html'] = value
end
end
end
core.display = {}
function core.display.add_to_container_from_map(tpl_args, frame, container, mapping)
local grpcont
local valid
local statcont = mw.html.create('span')
statcont
:attr('class', 'item-stats')
:done()
local count = 0
for _, group in ipairs(mapping) do
grpcont = {}
if group.func == nil then
for _, disp in ipairs(group) do
valid = true
-- No args to verify which means always valid
if disp.args == nil then
elseif type(disp.args) == 'table' then
for _, key in ipairs(disp.args) do
if tpl_args[key] == nil then
valid = false
break
end
end
elseif type(disp.args) == 'function' then
valid = disp.args(tpl_args, frame)
end
if valid then
grpcont[#grpcont+1] = disp.func(tpl_args, frame)
end
end
else
grpcont = group.func(tpl_args, frame)
end
if #grpcont > 0 then
count = count + 1
local header = ''
if group.header == nil then
elseif type(group.header) == 'function' then
header = group.header()
else
header = string.format('<em class="header">%s</em><br>', group.header)
end
statcont
:tag('span')
:attr('class', 'group ' .. (group.css_class or ''))
:wikitext(header .. table.concat(grpcont, '<br>'))
:done()
end
end
-- Don't add empty containers
if count > 0 then
container:node(statcont)
end
end
--
-- argument mapping
--
-- format:
-- tpl_args key = {
-- no_copy = true or nil -- When loading an base item, dont copy this key
-- property = 'prop', -- Property associated with this key
-- property_func = function or nil -- Function to unpack the property into a native lua value.
-- If not specified, func is used.
-- If neither is specified, value is copied as string
-- func = function or nil -- Function to unpack the argument into a native lua value and validate it.
-- If not specified, value will not be set.
-- default = object -- Default value if the parameter is nil
-- }
core.map = {
-- special params
html = {
no_copy = true,
field = 'html',
type = 'Text',
func = nil,
},
implicit_stat_text = {
field = 'implicit_stat_text',
type = 'Text',
func = function(tpl_args, frame)
tpl_args.implicit_stat_text = core.process_mod_stats(tpl_args, {type='implicit'})
end,
},
explicit_stat_text = {
field = 'explicit_stat_text',
type = 'Text',
func = function(tpl_args, frame)
tpl_args.explicit_stat_text = core.process_mod_stats(tpl_args, {type='explicit'})
if tpl_args.is_talisman or tpl_args.is_corrupted then
if tpl_args.explicit_stat_text == nil or tpl_args.explicit_stat_text == '' then
tpl_args.explicit_stat_text = m_util.html.poe_color('corrupted', i18n.tooltips.corrupted)
else
tpl_args.explicit_stat_text = (tpl_args.explicit_stat_text or '') .. '<br>' .. m_util.html.poe_color('corrupted', i18n.tooltips.corrupted)
end
end
end,
},
stat_text = {
field = 'stat_text',
type = 'Text',
func = function(tpl_args, frame)
local sep = ''
if tpl_args.implicit_stat_text and tpl_args.explicit_stat_text then
sep = string.format('<span class="item-stat-separator -%s"></span>', tpl_args.frame_type)
end
local text = (tpl_args.implicit_stat_text or '') .. sep .. (tpl_args.explicit_stat_text or '')
if string.len(text) > 0 then
tpl_args.stat_text = text
end
end,
},
-- processed in core.build_item_classes
class = {
no_copy = true,
field = 'class',
type = 'String',
func = m_util.cast.factory.table('class', {key='full', tbl=m_game.constants.item.class}),
},
-- generic
rarity = {
no_copy = true,
field = 'rarity',
type = 'String',
func = m_util.cast.factory.table('rarity', {key={'full', 'long_lower'}, tbl=m_game.constants.item.rarity, rtrkey='full'}),
},
name = {
no_copy = true,
field = 'name',
type = 'String',
func = nil,
},
size_x = {
field = 'size_x',
type = 'Integer',
func = m_util.cast.factory.number('size_x'),
},
size_y = {
field = 'size_y',
type = 'Integer',
func = m_util.cast.factory.number('size_y'),
},
drop_enabled = {
no_copy = true,
field = 'drop_enabled',
type = 'Boolean',
func = m_util.cast.factory.boolean('drop_enabled'),
default = true,
},
drop_level = {
no_copy = true,
field = 'drop_level',
type = 'Integer',
func = m_util.cast.factory.number('drop_level'),
},
drop_level_maximum = {
no_copy = true,
field = 'drop_level_maximum',
type = 'Integer',
func = m_util.cast.factory.number('drop_level_maximum'),
},
drop_leagues = {
no_copy = true,
field = 'drop_leagues',
type = 'List (,) of String',
func = m_util.cast.factory.assoc_table('drop_leagues', {tbl=m_game.constants.leagues, errmsg=i18n.errors.invalid_league}),
},
drop_areas = {
no_copy = true,
field = 'drop_areas',
type = 'List (,) of String',
func = function(tpl_args, frame)
if tpl_args.drop_areas == nil then
return
end
tpl_args.drop_areas = m_util.string.split(tpl_args.drop_areas, ',%s*')
tpl_args.drop_areas_data = m_util.cargo.array_query{
tables={'areas'},
fields={'areas._pageName', 'areas.name', 'areas.main_page'},
id_field='areas.id',
id_array=tpl_args.drop_areas,
}
-- TODO: Cargo: Raise error on missing
local areas = {}
for _, row in pairs(tpl_args.drop_areas) do
areas[#areas+1] = row['area.name']
end
end,
},
drop_text = {
no_copy = true,
field = 'drop_text',
type = 'String',
},
required_level = {
field = 'required_level_base',
type = 'Integer',
func = m_util.cast.factory.number('required_level'),
default = 1,
},
required_level_final = {
field = 'required_level',
type = 'Integer',
func = function(tpl_args, frame)
tpl_args.required_level_final = tpl_args.required_level
end,
default = 1,
},
required_dexterity = {
field = 'required_dexterity',
type = 'Integer',
func = m_util.cast.factory.number('required_dexterity'),
default = 0,
},
required_strength = {
field = 'required_strength',
type = 'Integer',
func = m_util.cast.factory.number('required_strength'),
default = 0,
},
required_intelligence = {
field = 'required_intelligence',
type = 'Integer',
func = m_util.cast.factory.number('required_intelligence'),
default = 0,
},
inventory_icon = {
no_copy = true,
field = 'inventory_icon',
type = 'String',
func = function(tpl_args, frame)
if tpl_args.class == 'Divination Card' then
tpl_args.inventory_icon = tpl_args.inventory_icon or 'Divination card'
end
tpl_args.inventory_icon_id = tpl_args.inventory_icon or tpl_args.name
tpl_args.inventory_icon = string.format(i18n.inventory_icon, tpl_args.inventory_icon_id)
end,
},
-- note: this must be called after inventory item to work correctly as it depends on tpl_args.inventory_icon_id being set
alternate_art_inventory_icons = {
no_copy = true,
field = 'alternate_art_inventory_icons',
type = 'List (,) of String',
func = function(tpl_args, frame)
local icons = {}
if tpl_args.alternate_art_inventory_icons ~= nil then
local names = m_util.string.split(tpl_args.alternate_art_inventory_icons, ',%s*')
for _, name in ipairs(names) do
icons[#icons+1] = string.format(i18n.inventory_icon, string.format('%s %s', tpl_args.inventory_icon_id, name))
end
end
tpl_args.alternate_art_inventory_icons = icons
end,
default = function () return {} end,
},
cannot_be_traded_or_modified = {
no_copy = true,
field = 'cannot_be_traded_or_modified',
type = 'Boolean',
func = m_util.cast.factory.boolean('cannot_be_traded_or_modified'),
default = false,
},
help_text = {
field = 'help_text',
type = 'Text',
func = nil,
},
flavour_text = {
no_copy = true,
field = 'flavour_text',
type = 'Text',
func = nil,
},
tags = {
field = 'tags',
type = 'List (,) of String',
func = m_util.cast.factory.assoc_table('tags', {
tbl = m_game.constants.tags,
errmsg = i18n.errors.invalid_tag,
}),
},
metadata_id = {
no_copy = true,
field = 'metadata_id',
type = 'String',
func = nil,
},
is_corrupted = {
no_copy = true,
field = 'is_corrupted',
type = 'Boolean',
func = m_util.cast.factory.boolean('is_corrupted'),
default = false,
},
is_relic = {
no_copy = true,
field = 'is_relic',
type = 'Boolean',
func = function(tpl_args, frame)
m_util.cast.factory.boolean('is_relic')(tpl_args, frame)
if tpl_args.is_relic == true and tpl_args.rarity ~= 'Unique' then
error(i18n.errors.non_unique_relic)
end
end,
default = false,
},
purchase_costs = {
func = function(tpl_args, frame)
local purchase_costs = {}
for _, rarity_names in ipairs(m_game.constants.item.rarity) do
local rtbl = {}
local prefix = string.format('purchase_cost_%s', rarity_names.long_lower)
local i = 1
while i ~= -1 do
prefix = prefix .. i
local values = {
name = tpl_args[prefix .. '_name'],
amount = tonumber(tpl_args[prefix .. '_amount']),
rarity = rarity_names.long_upper,
}
if values.name ~= nil and values.amount ~= nil then
rtbl[#rtbl+1] = values
i = i + 1
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_purchase_costs',
amount = values.amount,
name = values.name,
rarity = values.rarity,
}
else
i = -1
end
end
purchase_costs[rarity_names.long_lower] = rtbl
end
tpl_args.purchase_costs = purchase_costs
end,
func_fetch = function(tpl_args, frame)
if tpl_args.rarity ~= 'Unique' then
return
end
local results = cargo.query(
'items, item_purchase_costs',
'item_purchase_costs.amount=item_purchase_costs.amount,item_purchase_costs.name=item_purchase_costs.name,item_purchase_costs.rarity=item_purchase_costs.rarity',
{
join = 'items._pageID=item_purchase_costs._pageID',
where = string.format('items._pageName="%s" AND item_purchase_costs.rarity="Unique"', tpl_args.base_item_page),
-- Workaround: Fix cargo duplicates
groupBy = 'item_purchase_costs._pageName, item_purchase_costs.name, item_purchase_costs.rarity',
}
)
for _, row in ipairs(results) do
local values = {
rarity = row['item_purchase_costs.rarity'],
name = row['item_purchase_costs.name'],
amount = tonumber(row['item_purchase_costs.amount']),
}
local datavar = tpl_args.purchase_costs[string.lower(values.rarity)]
datavar[#datavar+1] = values
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_purchase_costs',
amount = values.amount,
name = values.name,
rarity = values.rarity,
}
end
end,
},
--
-- specific section
--
-- Most item classes
quality = {
no_copy = true,
field = 'quality',
type = 'Integer',
-- Can be set manually, but default to Q20 for unique weapons/body armours
-- Also must copy to stat for the stat adjustments to work properly
func = function(tpl_args, frame)
local quality = tonumber(tpl_args.quality)
--
if quality == nil then
if tpl_args.rarity ~= 'Unique' then
quality = 0
elseif core.class_groups.weapons.keys[tpl_args.class] or core.class_groups.armor.keys[tpl_args.class] then
quality = 20
else
quality = 0
end
end
tpl_args.quality = quality
local stat = {
min = quality,
max = quality,
avg = quality,
}
h.stats_update(tpl_args, 'quality', stat, nil, '_stats')
if tpl_args.class == 'Utility Flasks' or tpl_args.class == 'Critical Utility Flasks' then
h.stats_update(tpl_args, 'quality_flask_duration', stat, nil, '_stats')
-- quality is added to quantity for maps
elseif tpl_args.class == 'Maps' then
h.stats_update(tpl_args, 'map_item_drop_quantity_+%', stat, nil, '_stats')
end
end,
},
-- amulets
is_talisman = {
field = 'is_talisman',
type = 'Boolean',
func = m_util.cast.factory.boolean('is_talisman'),
default = false,
},
talisman_tier = {
field = 'talisman_tier',
type = 'Integer',
func = m_util.cast.factory.number('talisman_tier'),
},
-- flasks
charges_max = {
field = 'charges_max',
type = 'Integer',
func = m_util.cast.factory.number('charges_max'),
},
charges_per_use = {
field = 'charges_per_use',
type = 'Integer',
func = m_util.cast.factory.number('charges_per_use'),
},
flask_mana = {
field = 'mana',
type = 'Integer',
func = m_util.cast.factory.number('flask_mana'),
},
flask_life = {
field = 'life',
type = 'Integer',
func = m_util.cast.factory.number('flask_life'),
},
flask_duration = {
field = 'duration',
type = 'Integer',
func = m_util.cast.factory.number('flask_duration'),
},
buff_id = {
field = 'id',
type = 'String',
func = nil,
},
buff_values = {
field = 'values',
type = 'List (,) of Integer',
func = function(tpl_args, frame)
local values = {}
local i = 0
repeat
i = i + 1
local key = 'buff_value' .. i
values[i] = tonumber(tpl_args[key])
tpl_args[key] = nil
until values[i] == nil
tpl_args.buff_values = values
end,
},
buff_stat_text = {
field = 'stat_text',
type = 'String',
func = nil,
},
buff_icon = {
field = 'icon',
type = 'String',
func = function(tpl_args, frame)
tpl_args.buff_icon = string.format(i18n.status_icon, tpl_args.name)
end,
},
-- weapons
critical_strike_chance = {
field = 'critical_strike_chance',
type = 'Integer',
func = m_util.cast.factory.number('critical_strike_chance'),
},
attack_speed = {
field = 'attack_speed',
type = 'Integer',
func = m_util.cast.factory.number('attack_speed'),
},
range = {
field = 'range',
type = 'Integer',
func = m_util.cast.factory.number('range'),
},
physical_damage_min = {
field = 'physical_damage_min',
type = 'Integer',
func = m_util.cast.factory.number('physical_damage_min'),
},
physical_damage_max = {
field = 'physical_damage_max',
type = 'Integer',
func = m_util.cast.factory.number('physical_damage_max'),
},
fire_damage_min = {
field = 'fire_damage_min',
type = 'Integer',
func = m_util.cast.factory.number('fire_damage_min'),
default = 0,
},
fire_damage_max = {
field = 'fire_damage_max',
type = 'Integer',
func = m_util.cast.factory.number('fire_damage_max'),
default = 0,
},
cold_damage_min = {
field = 'cold_damage_min',
type = 'Integer',
func = m_util.cast.factory.number('cold_damage_min'),
default = 0,
},
cold_damage_max = {
field = 'cold_damage_max',
type = 'Integer',
func = m_util.cast.factory.number('cold_damage_max'),
default = 0,
},
lightning_damage_min = {
field = 'lightning_damage_min',
type = 'Integer',
func = m_util.cast.factory.number('lightning_damage_min'),
default = 0,
},
lightning_damage_max = {
field = 'lightning_damage_max',
type = 'Integer',
func = m_util.cast.factory.number('lightning_damage_max'),
default = 0,
},
chaos_damage_min = {
field = 'chaos_damage_min',
type = 'Integer',
func = m_util.cast.factory.number('chaos_damage_min'),
default = 0,
},
chaos_damage_max = {
field = 'chaos_damage_max',
type = 'Integer',
func = m_util.cast.factory.number('chaos_damage_max'),
default = 0,
},
-- armor-type stuff
armour = {
field = 'armour',
type = 'Integer',
func = m_util.cast.factory.number('armour'),
default = 0,
},
energy_shield = {
field = 'energy_shield',
type = 'Integer',
func = m_util.cast.factory.number('energy_shield'),
default = 0,
},
evasion = {
field = 'evasion',
type = 'Integer',
func = m_util.cast.factory.number('evasion'),
default = 0,
},
-- shields
block = {
field = 'block',
type = 'Integer',
func = m_util.cast.factory.number('block'),
},
-- skill gem stuff
gem_description = {
field = 'gem_description',
type = 'Text',
func = nil,
},
dexterity_percent = {
field = 'dexterity_percent',
type = 'Integer',
func = m_util.cast.factory.percentage('dexterity_percent'),
},
strength_percent = {
field = 'strength_percent',
type = 'Integer',
func = m_util.cast.factory.percentage('strength_percent'),
},
intelligence_percent = {
field = 'intelligence_percent',
type = 'Integer',
func = m_util.cast.factory.percentage('intelligence_percent'),
},
primary_attribute = {
field = 'primary_attribute',
type = 'String',
func = function(tpl_args, frame)
for _, attr in ipairs(m_game.constants.attributes) do
local val = tpl_args[attr.long_lower .. '_percent']
if val and val >= 60 then
tpl_args['primary_attribute'] = attr.long_upper
return
end
end
tpl_args['primary_attribute'] = 'None'
end,
},
gem_tags = {
field = 'gem_tags',
type = 'List (,) of String',
-- TODO: default rework
func = m_util.cast.factory.array_table('gem_tags', {
tbl = m_game.constants.item.gem_tags,
errmsg = i18n.errors.invalid_tag,
}),
default = function () return {} end,
},
skill_screenshot = {
field = 'skill_screenshot',
type = 'Page',
func = function(tpl_args, frame)
tpl_args.skill_screenshot = string.format(i18n.skill_screenshot, tpl_args.name)
end,
},
-- Support gems only
support_gem_letter = {
field = 'support_gem_letter',
type = 'String(size=1)',
func = nil,
},
support_gem_letter_html = {
field = 'support_gem_letter_html',
type = 'Text',
func = function(tpl_args, frame)
if tpl_args.support_gem_letter == nil then
return
end
-- TODO replace this with a loop possibly
local css_map = {
strength = 'red',
intelligence = 'blue',
dexterity = 'green',
}
local id
for k, v in pairs(css_map) do
k = string.format('%s_percent', k)
if tpl_args[k] and tpl_args[k] > 50 then
id = v
break
end
end
if id ~= nil then
local container = mw.html.create('span')
container
:attr('class', string.format('support-gem-id-%s', id))
:wikitext(tpl_args.support_gem_letter)
:done()
tpl_args.support_gem_letter_html = tostring(container)
end
end,
},
-- Maps
map_tier = {
field = 'tier',
type = 'Integer',
func = m_util.cast.factory.number('tier'),
},
map_guild_character = {
field = 'guild_character',
type = 'String(size=1)',
func = nil,
},
map_area_id = {
field = 'area_id',
type = 'String',
func = nil, -- TODO: Validate against a query?
},
map_area_level = {
field = 'area_level',
type = 'Integer',
func = m_util.cast.factory.number('map_area_level'),
},
unique_map_guild_character = {
ield = 'guild_character',
type = 'String(size=1)',
func_copy = function(tpl_args, frame)
tpl_args.map_guild_character = tpl_args.unique_map_guild_character
end,
func = nil,
},
unique_map_area_id = {
field = 'unique_area_id',
type = 'String',
func = nil, -- TODO: Validate against a query?
func_copy = function(tpl_args, frame)
tpl_args.map_area_id = tpl_args.unique_map_area_id
end,
},
unique_map_area_level = {
field = 'unique_area_level',
type = 'Integer',
func = m_util.cast.factory.number('unique_map_area_level'),
func_copy = function(tpl_args, frame)
tpl_args.map_area_level = tpl_args.unique_map_area_level
end,
},
--
-- Currency-like items
--
stack_size = {
field = 'stack_size',
type = 'Integer',
func = m_util.cast.factory.number('stack_size'),
},
stack_size_currency_tab = {
field = 'stack_size_currency_tab',
type = 'Integer',
func = m_util.cast.factory.number('stack_size_currency_tab'),
},
description = {
field = 'description',
type = 'Text',
func = nil,
},
cosmetic_type = {
field = 'cosmetic_type',
type = 'String',
func = nil,
},
-- for essences
is_essence = {
field = nil,
func = m_util.cast.factory.boolean('is_essence'),
default = false,
},
essence_level_restriction = {
field = 'level_restriction',
type = 'Integer',
func = m_util.cast.factory.number('essence_level_restriction'),
},
essence_level = {
field = 'level',
type = 'Integer',
func = m_util.cast.factory.number('essence_level'),
},
--
-- hideout doodads (HideoutDoodads.dat)
--
is_master_doodad = {
field = 'is_master_doodad',
type = 'Boolean',
func = m_util.cast.factory.boolean('is_master_doodad'),
},
master = {
field = 'master',
type = 'String',
-- todo validate against list of master names
func = m_util.cast.factory.table('master', {key='full', tbl=m_game.constants.masters}),
},
master_level_requirement = {
field = 'level_requirement',
type = 'Integer',
func = m_util.cast.factory.number('master_level_requirement'),
},
master_favour_cost = {
field = 'favour_cost',
type = 'Integer',
func = m_util.cast.factory.number('master_favour_cost'),
},
variation_count = {
field = 'variation_count',
type = 'Integer',
func = m_util.cast.factory.number('variation_count'),
},
-- Propehcy
prophecy_id = {
field = 'prophecy_id',
type = 'String',
func = nil,
},
prediction_text = {
field = 'prediction_text',
type = 'Text',
func = nil,
},
seal_cost = {
field = 'seal_cost',
type = 'Integer',
func = m_util.cast.factory.number('seal_cost'),
},
prophecy_reward = {
field = 'reward',
type = 'Text',
func = nil,
},
prophecy_objective = {
field = 'objective',
type = 'Text',
func = nil,
},
-- Divination cards
card_art = {
field = 'card_art',
type = 'Page',
func = function(tpl_args, frame)
tpl_args.card_art = string.format(i18n.divination_card_art, tpl_args.name)
end,
},
-- ------------------------------------------------------------------------
-- derived stats
-- ------------------------------------------------------------------------
-- For rarity != normal, rarity already verified
base_item = {
no_copy = true,
field = 'base_item',
type = 'String',
func = function(tpl_args, frame)
tpl_args.base_item = tpl_args.base_item_data['items.name']
end,
},
base_item_id = {
no_copy = true,
field = 'base_item_id',
type = 'String',
func = function(tpl_args, frame)
tpl_args.base_item_id = tpl_args.base_item_data['items.metadata_id']
end,
},
base_item_page = {
no_copy = true,
field = 'base_item_page',
type = 'Page',
func = function(tpl_args, frame)
tpl_args.base_item_page = tpl_args.base_item_data['items._pageName']
end,
},
name_list = {
no_copy = true,
field = 'name_list',
type = 'List (,) of String',
func = function(tpl_args, frame)
if tpl_args.name_list ~= nil then
tpl_args.name_list = m_util.string.split(tpl_args.name_list, ',%s*')
tpl_args.name_list[#tpl_args.name_list+1] = tpl_args.name
else
tpl_args.name_list = {tpl_args.name}
end
end,
},
frame_type = {
no_copy = true,
field = 'frame_type',
type = 'String',
property = nil,
func = function(tpl_args, frame)
if tpl_args.name == 'Prophecy' or tpl_args.base_item == 'Prophecy' then
tpl_args.frame_type = 'prophecy'
return
end
local var = core.class_specifics[tpl_args.class]
if var ~= nil and var.frame_type ~= nil then
tpl_args.frame_type = var.frame_type
return
end
if tpl_args.is_relic then
tpl_args.frame_type = 'relic'
return
end
tpl_args.frame_type = string.lower(tpl_args.rarity)
end,
},
--
-- args populated by mod validation
--
mods = {
no_copy = true,
field = 'mods',
type = 'List (,) of String',
default = function () return {} end,
},
implicit_mods = {
field = 'implicit_mods',
type = 'List (,) of String',
func_copy = function (tpl_args)
tpl_args.implicit_mods = m_util.string.split(tpl_args.implicit_mods, ',%s*')
for _, modid in ipairs(tpl_args.implicit_mods) do
tpl_args.mods[#tpl_args.mods+1] = modid
tpl_args._mods[#tpl_args._mods+1] = {
result=nil,
modid=modid,
type='implicit',
}
end
end,
default = function () return {} end,
},
explicit_mods = {
field = 'explicit_mods',
type = 'List (,) of String',
default = function () return {} end,
},
physical_damage_html = {
no_copy = true,
field = 'physical_damage_html',
type = 'Text',
func = core.factory.damage_html{key='physical'},
},
fire_damage_html = {
no_copy = true,
field = 'fire_damage_html',
type = 'Text',
func = core.factory.damage_html{key='fire'},
},
cold_damage_html = {
no_copy = true,
field = 'cold_damage_html',
type = 'Text',
func = core.factory.damage_html{key='cold'},
},
lightning_damage_html = {
no_copy = true,
field = 'lightning_damage_html',
type = 'Text',
func = core.factory.damage_html{key='lightning'},
},
chaos_damage_html = {
no_copy = true,
field = 'chaos_damage_html',
type = 'Text',
func = core.factory.damage_html{key='chaos'},
},
damage_avg = {
no_copy = true,
field = 'damage_avg',
type = 'Text',
func = function(tpl_args, frame)
local dmg = {min=0, max=0}
for key, _ in pairs(dmg) do
for _, data in ipairs(m_game.constants.damage_types) do
dmg[key] = dmg[key] + tpl_args[string.format('%s_damage_%s_range_average', data.short_lower, key)]
end
end
dmg = (dmg.min + dmg.max) / 2
tpl_args.damage_avg = dmg
end,
},
damage_html = {
no_copy = true,
field = 'damage_html',
type = 'Text',
func = function(tpl_args, frame)
local text = {}
for _, data in ipairs(m_game.constants.damage_types) do
local value = tpl_args[data.short_lower .. '_damage_html']
if value ~= nil then
text[#text+1] = value
end
end
if #text > 0 then
tpl_args.damage_html = table.concat(text, '<br>')
end
end,
},
item_limit = {
no_copy = true,
field = 'item_limit',
type = 'Integer',
func = m_util.cast.factory.number('item_limit'),
},
jewel_radius_html = {
no_copy = true,
field = 'radius_html',
type = 'Text',
func = function(tpl_args, frame)
local radius = tpl_args._stats.local_jewel_effect_base_radius
if radius then
radius = radius.min
tpl_args.jewel_radius_html = string.format('%s (%i)', (m_game.constants.item.jewel_radius_to_size[radius] or '?'), radius)
end
end,
},
drop_areas_html = {
no_copy = true,
field = 'drop_areas_html',
type = 'Text',
func = function(tpl_args, frame)
if tpl_args.drop_areas_data == nil then
return
end
if tpl_args.drop_areas_html ~= nil then
return
end
local areas = {}
for _, data in pairs(tpl_args.drop_areas_data) do
local page
if data['areas.main_page'] == '' then
page = data['areas._pageName']
else
page = data['areas.main_page']
end
areas[#areas+1] = string.format('[[%s]]', page)
end
tpl_args.drop_areas_html = table.concat(areas, ', ')
end,
},
}
core.stat_map = {
required_level_final = {
field = 'required_level',
stats_add = {
'local_level_requirement_+',
},
stats_override = {
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=1, max=1},
},
minimum = 1,
html_fmt_options = {
fmt = '%i',
},
},
range = {
field = 'range',
stats_add = {
'local_weapon_range_+',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
physical_damage_min = {
field = 'physical_damage_min',
stats_add = {
'local_minimum_added_physical_damage',
},
stats_increased = {
'local_physical_damage_+%',
'quality',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
physical_damage_max = {
field = 'physical_damage_max',
stats_add = {
'local_maximum_added_physical_damage',
},
stats_increased = {
'local_physical_damage_+%',
'quality',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
fire_damage_min = {
field = 'fire_damage_min',
stats_add = {
'local_minimum_added_fire_damage',
},
minimum = 0,
html_fmt_options = {
color = 'fire',
fmt = '%i',
},
},
fire_damage_max = {
field = 'fire_damage_max',
stats_add = {
'local_maximum_added_fire_damage',
},
minimum = 0,
html_fmt_options = {
color = 'fire',
fmt = '%i',
},
},
cold_damage_min = {
field = 'cold_damage_min',
stats_add = {
'local_minimum_added_cold_damage',
},
minimum = 0,
html_fmt_options = {
color = 'cold',
fmt = '%i',
},
},
cold_damage_max = {
field = 'cold_damage_max',
stats_add = {
'local_maximum_added_cold_damage',
},
minimum = 0,
html_fmt_options = {
color = 'cold',
fmt = '%i',
},
},
lightning_damage_min = {
field = 'lightning_damage_min',
stats_add = {
'local_minimum_added_lightning_damage',
},
minimum = 0,
html_fmt_options = {
color = 'lightning',
fmt = '%i',
},
},
lightning_damage_max = {
field = 'lightning_damage_max',
stats_add = {
'local_maximum_added_lightning_damage',
},
minimum = 0,
html_fmt_options = {
color = 'lightning',
fmt = '%i',
},
},
chaos_damage_min = {
field = 'chaos_damage_min',
stats_add = {
'local_minimum_added_chaos_damage',
},
minimum = 0,
html_fmt_options = {
color = 'chaos',
fmt = '%i',
},
},
chaos_damage_max = {
field = 'chaos_damage_max',
stats_add = {
'local_maximum_added_chaos_damage',
},
minimum = 0,
html_fmt_options = {
color = 'chaos',
fmt = '%i',
},
},
critical_strike_chance = {
field = 'critical_strike_chance',
stats_add = {
'local_critical_strike_chance',
},
stats_increased = {
'local_critical_strike_chance_+%',
},
stats_override = {
['local_weapon_always_crit'] = {min=100, max=100},
},
minimum = 0,
html_fmt_options = {
fmt = '%.2f%%',
},
},
attack_speed = {
field = 'attack_speed',
stats_increased = {
'local_attack_speed_+%',
},
minimum = 0,
html_fmt_options = {
fmt = '%.2f',
},
},
flask_life = {
field = 'life',
stats_add = {
'local_flask_life_to_recover',
},
stats_increased = {
'local_flask_life_to_recover_+%',
'local_flask_amount_to_recover_+%',
'quality',
},
html_fmt_options = {
fmt = '%i',
},
},
flask_mana = {
field = 'mana',
stats_add = {
'local_flask_mana_to_recover',
},
stats_increased = {
'local_flask_mana_to_recover_+%',
'local_flask_amount_to_recover_+%',
'quality',
},
},
flask_duration = {
field = 'duration',
stats_increased = {
'local_flask_duration_+%',
-- regular quality isn't used here because it doesn't increase duration of life/mana/hybrid flasks
'quality_flask_duration',
},
stats_increased_inverse = {
'local_flask_recovery_speed_+%',
},
minimum = 0,
html_fmt_options = {
fmt = '%.2f',
},
},
charges_per_use = {
field = 'charges_per_use',
stats_increased = {
'local_charges_used_+%',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
charges_max = {
field = 'charges_max',
stats_add = {
'local_extra_max_charges',
},
stats_increased = {
'local_max_charges_+%',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
block = {
field = 'block',
stats_add = {
'local_additional_block_chance_%',
},
minimum = 0,
html_fmt_options = {
fmt = '%i%%',
},
},
armour = {
field = 'armour',
stats_add = {
'local_base_physical_damage_reduction_rating',
},
stats_increased = {
'local_physical_damage_reduction_rating_+%',
'local_armour_and_energy_shield_+%',
'local_armour_and_evasion_+%',
'local_armour_and_evasion_and_energy_shield_+%',
'quality',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
evasion = {
field = 'evasion',
stats_add = {
'local_base_evasion_rating',
},
stats_increased = {
'local_evasion_rating_+%',
'local_evasion_and_energy_shield_+%',
'local_armour_and_evasion_+%',
'local_armour_and_evasion_and_energy_shield_+%',
'quality',
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
energy_shield = {
field = 'energy_shield',
stats_add = {
'local_energy_shield'
},
stats_increased = {
'local_energy_shield_+%',
'local_armour_and_energy_shield_+%',
'local_evasion_and_energy_shield_+%',
'local_armour_and_evasion_and_energy_shield_+%',
'quality',
},
stats_override = {
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
required_dexterity = {
field = 'required_dexterity',
stats_add = {
'local_dexterity_requirement_+'
},
stats_increased = {
'local_dexterity_requirement_+%',
'local_attribute_requirements_+%',
},
stats_override = {
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
required_intelligence = {
field = 'required_intelligence',
stats_add = {
'local_intelligence_requirement_+'
},
stats_increased = {
'local_intelligence_requirement_+%',
'local_attribute_requirements_+%',
},
stats_override = {
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
required_strength = {
field = 'required_strength',
stats_add = {
'local_strength_requirement_+'
},
stats_increased = {
'local_strength_requirement_+%',
'local_attribute_requirements_+%',
},
stats_override = {
['local_unique_tabula_rasa_no_requirement_or_energy_shield'] = {min=0, max=0},
},
minimum = 0,
html_fmt_options = {
fmt = '%i',
},
},
map_area_level = {
field = 'map_area_level',
stats_override = {
['map_item_level_override'] = true,
},
},
}
core.dps_map = {
{
name = 'physical_dps',
field = 'physical_dps',
damage_args = {'physical_damage', },
label = i18n.item_table.physical_dps,
label_infobox = i18n.tooltips.physical_dps,
html_fmt_options = {
color = 'value',
fmt = '%.1f',
},
},
{
name = 'fire_dps',
field = 'fire_dps',
damage_args = {'fire_damage'},
label = i18n.item_table.fire_dps,
label_infobox = i18n.tooltips.fire_dps,
html_fmt_options = {
color = 'fire',
fmt = '%.1f',
},
},
{
name = 'cold_dps',
field = 'cold_dps',
damage_args = {'cold_damage'},
label = i18n.item_table.cold_dps,
label_infobox = i18n.tooltips.cold_dps,
html_fmt_options = {
color = 'cold',
fmt = '%.1f',
},
},
{
name = 'lightning_dps',
field = 'lightning_dps',
damage_args = {'lightning_damage'},
label = i18n.item_table.lightning_dps,
label_infobox = i18n.tooltips.lightning_dps,
html_fmt_options = {
color = 'lightning',
fmt = '%.1f',
},
},
{
name = 'chaos_dps',
field = 'chaos_dps',
damage_args = {'chaos_damage'},
label = i18n.item_table.chaos_dps,
label_infobox = i18n.tooltips.chaos_dps,
html_fmt_options = {
color = 'chaos',
fmt = '%.1f',
},
},
{
name = 'elemental_dps',
field = 'elemental_dps',
damage_args = {'fire_damage', 'cold_damage', 'lightning_damage'},
label = i18n.item_table.elemental_dps,
label_infobox = i18n.tooltips.elemental_dps,
html_fmt_options = {
color = 'value',
fmt = '%.1f',
},
},
{
name = 'poison_dps',
field = 'poison_dps',
damage_args = {'physical_damage', 'chaos_damage'},
label = i18n.item_table.poison_dps,
label_infobox = i18n.tooltips.poison_dps,
html_fmt_options = {
color = 'value',
fmt = '%.1f',
},
},
{
name = 'dps',
field = 'dps',
damage_args = {'physical_damage', 'fire_damage', 'cold_damage', 'lightning_damage', 'chaos_damage'},
label = i18n.item_table.dps,
label_infobox = i18n.tooltips.dps,
html_fmt_options = {
color = 'value',
fmt = '%.1f',
},
},
}
core.cargo = {}
core.cargo.items = {
table = 'items',
fields = {
html = core.map.html,
implicit_stat_text = core.map.implicit_stat_text,
explicit_stat_text = core.map.explicit_stat_text,
stat_text = core.map.stat_text,
class = core.map.class,
rarity = core.map.rarity,
name = core.map.name,
size_x = core.map.size_x,
size_y = core.map.size_y,
drop_enabled = core.map.drop_enabled,
drop_level = core.map.drop_level,
drop_level_maximum = core.map.drop_level_maximum,
drop_leagues = core.map.drop_leagues,
drop_areas = core.map.drop_areas,
drop_areas_html = core.map.drop_areas_html,
drop_text = core.map.drop_text,
required_level = core.map.required_level,
required_level_final = core.map.required_level_final,
required_dexterity = core.map.required_dexterity,
required_strength = core.map.required_strength,
required_intelligence = core.map.required_intelligence,
inventory_icon = core.map.inventory_icon,
alternate_art_inventory_icons = core.map.alternate_art_inventory_icons,
buff_icon = core.map.buff_icon,
cannot_be_traded_or_modified = core.map.cannot_be_traded_or_modified,
help_text = core.map.help_text,
flavour_text = core.map.flavour_text,
tags = core.map.tags,
metadata_id = core.map.metadata_id,
is_corrupted = core.map.is_corrupted,
is_relic = core.map.is_relic,
quality = core.map.quality,
base_item = core.map.base_item,
base_item_id = core.map.base_item_id,
base_item_page = core.map.base_item_page,
frame_type = core.map.frame_type,
mods = core.map.mods,
explicit_mods = core.map.explicit_mods,
implicit_mods = core.map.implicit_mods,
name_list = core.map.name_list,
description = core.map.description,
},
}
core.cargo.item_purchase_costs = {
table = 'item_purchase_costs',
fields = {
amount = {
field = 'amount',
type = 'Integer',
},
name = {
field = 'name',
type = 'String',
},
rarity = {
field = 'rarity',
type = 'String',
},
},
}
core.cargo.item_stats = {
table = 'item_stats',
fields = {
id = {
field = 'id',
type = 'String',
},
min = {
field = 'min',
type = 'Integer',
},
max = {
field = 'max',
type = 'Integer',
},
avg = {
field = 'avg',
type = 'Integer',
},
is_implicit = {
field = 'is_implicit',
type = 'Boolean',
},
},
}
-- There probably will be a table named "buffs" in the future, so "item_buffs" is the best solution here
core.cargo.item_buffs = {
table = 'item_buffs',
fields = {
id = core.map.buff_id,
values = core.map.buff_values,
stat_text = core.map.buff_stat_text,
icon = core.map.buff_icon,
},
}
core.cargo.upgraded_from_sets = {
table = 'upgraded_from_sets',
fields = {
set_id = {
field = 'set_id',
type = 'Integer',
},
text = {
field = 'text',
type = 'Text',
},
}
}
core.cargo.upgraded_from_groups = {
table = 'upgraded_from_groups',
fields = {
group_id = {
field = 'group_id',
type = 'Integer',
},
set_id = {
field = 'set_id',
type = 'Integer',
},
item_id = {
field = 'item_id',
type = 'String',
},
item_name = {
field = 'item_name',
type = 'String',
},
item_page = {
field = 'item_page',
type = 'Page',
},
integer = {
field = 'amount',
type = 'Integer',
},
notes = {
field = 'notes',
type = 'Text',
},
}
}
core.cargo.amulets = {
table = 'amulets',
fields = {
is_talisman = core.map.is_talisman,
talisman_tier = core.map.talisman_tier,
},
}
core.cargo.flasks = {
table = 'flasks',
fields = {
-- All flasks
duration = core.map.flask_duration,
charges_max = core.map.charges_max,
charges_per_use = core.map.charges_per_use,
-- Life/Mana/Hybrid flasks
life = core.map.flask_life,
mana = core.map.flask_mana,
},
}
core.cargo.weapons = {
table = 'weapons',
fields = {
critical_strike_chance = core.map.critical_strike_chance,
attack_speed = core.map.attack_speed,
range = core.map.range,
physical_damage_min = core.map.physical_damage_min,
physical_damage_max = core.map.physical_damage_max,
physical_damage_html = core.map.physical_damage_html,
fire_damage_html = core.map.fire_damage_html,
cold_damage_html = core.map.cold_damage_html,
lightning_damage_html = core.map.lightning_damage_html,
chaos_damage_html = core.map.chaos_damage_html,
damage_avg = core.map.damage_avg,
damage_html = core.map.damage_html,
-- Values added via stat population
fire_damage_min = core.map.fire_damage_min,
fire_damage_max = core.map.fire_damage_max,
cold_damage_min = core.map.cold_damage_min,
cold_damage_max = core.map.cold_damage_max,
lightning_damage_min = core.map.lightning_damage_min,
lightning_damage_max = core.map.lightning_damage_max,
chaos_damage_min = core.map.chaos_damage_min,
chaos_damage_max = core.map.chaos_damage_max,
},
}
core.cargo.armours = {
table = 'armours',
fields = {
armour = core.map.armour,
energy_shield = core.map.energy_shield,
evasion = core.map.evasion,
},
}
core.cargo.shields = {
table = 'shields',
fields = {
block = core.map.block,
}
}
core.cargo.skill_gems = {
table = 'skill_gems',
fields = {
gem_description = core.map.gem_description,
dexterity_percent = core.map.dexterity_percent,
strength_percent = core.map.strength_percent,
intelligence_percent = core.map.intelligence_percent,
primary_attribute = core.map.primary_attribute,
gem_tags = core.map.gem_tags,
skill_screenshot = core.map.skill_screenshot,
-- Support Skill Gems
support_gem_letter = core.map.support_gem_letter,
support_gem_letter_html = core.map.support_gem_letter_html,
},
}
core.cargo.maps = {
table = 'maps',
fields = {
tier = core.map.map_tier,
guild_character = core.map.map_guild_character,
area_id = core.map.map_area_id,
unique_area_id = core.map.unique_map_area_id,
-- REMOVE?
area_level = core.map.map_area_level,
unique_area_level = core.map.unique_map_area_level,
},
}
core.cargo.stackables = {
table = 'stackables',
fields = {
stack_size = core.map.stack_size,
stack_size_currency_tab = core.map.stack_size_currency_tab,
cosmetic_type = core.map.cosmetic_type,
},
}
core.cargo.essences = {
table = 'essences',
fields = {
level_restriction = core.map.essence_level_restriction,
level = core.map.essence_level,
},
}
core.cargo.hideout_doodads = {
table = 'hideout_doodads',
fields = {
is_master_doodad = core.map.is_master_doodad,
master = core.map.master,
master_level_requirement = core.map.master_level_requirement,
master_favour_cost = core.map.master_favour_cost,
variation_count = core.map.variation_count,
},
}
core.cargo.prophecies = {
table = 'prophecies',
fields = {
prophecy_id = core.map.prophecy_id,
prediction_text = core.map.prediction_text,
seal_cost = core.map.seal_cost,
objective = core.map.prophecy_objective,
reward = core.map.prophecy_reward,
},
}
core.cargo.divination_cards = {
table = 'divination_cards',
fields = {
card_art = core.map.card_art,
},
}
core.cargo.jewels = {
table = 'jewels',
fields = {
item_limit = core.map.item_limit,
radius_html = core.map.jewel_radius_html,
},
}
-- TODO: Second pass for i18n item classes
-- base item is default, but will be validated later
-- Notes:
-- inventory_icon must always be before alternate_art_inventory_icons
-- is_relic after rarity
core.default_args = {
'rarity', 'name', 'name_list', 'size_x', 'size_y',
'drop_enabled', 'drop_level', 'drop_level_maximum', 'drop_leagues', 'drop_areas', 'drop_text', 'required_level', 'required_level_final',
'inventory_icon', 'alternate_art_inventory_icons', 'flavour_text',
'cannot_be_traded_or_modified', 'help_text', 'tags', 'metadata_id', 'is_corrupted', 'is_relic', 'purchase_costs', 'mods', 'implicit_mods', 'explicit_mods',
'drop_areas_html',
}
-- frame_type is needed in stat_text
core.late_args = {'frame_type', 'implicit_stat_text', 'explicit_stat_text', 'stat_text'}
core.prophecy_args = {'prophecy_id', 'prediction_text', 'seal_cost', 'prophecy_objective', 'prophecy_reward'}
core.tables = {'items'}
core.class_groups = {
flasks = {
tables = {'flasks'},
keys = {['Life Flasks'] = true, ['Mana Flasks'] = true, ['Hybrid Flasks'] = true, ['Utility Flasks'] = true, ['Critical Utility Flasks'] = true},
args = {'quality', 'flask_duration', 'charges_max', 'charges_per_use'},
},
weapons = {
tables = {'weapons'},
keys = {['Claws'] = true, ['Daggers'] = true, ['Wands'] = true, ['One Hand Swords'] = true, ['Thrusting One Hand Swords'] = true, ['One Hand Axes'] = true, ['One Hand Maces'] = true, ['Bows'] = true, ['Staves'] = true, ['Two Hand Swords'] = true, ['Two Hand Axes'] = true, ['Two Hand Maces'] = true, ['Sceptres'] = true, ['Fishing Rods'] = true},
args = {'quality', 'required_dexterity', 'required_intelligence', 'required_strength', 'critical_strike_chance', 'attack_speed', 'physical_damage_min', 'physical_damage_max', 'lightning_damage_min', 'lightning_damage_max', 'cold_damage_min', 'cold_damage_max', 'fire_damage_min', 'fire_damage_max', 'chaos_damage_min', 'chaos_damage_max', 'range'},
late_args = {'physical_damage_html', 'fire_damage_html', 'cold_damage_html', 'lightning_damage_html', 'chaos_damage_html', 'damage_avg', 'damage_html'},
},
gems = {
tables = {'skill_gems'},
keys = {['Active Skill Gems'] = true, ['Support Skill Gems'] = true},
args = {'dexterity_percent', 'strength_percent', 'intelligence_percent', 'primary_attribute', 'gem_tags'},
},
armor = {
tables = {'armours'},
keys = {['Gloves'] = true, ['Boots'] = true, ['Body Armours'] = true, ['Helmets'] = true, ['Shields'] = true},
args = {'quality', 'required_dexterity', 'required_intelligence', 'required_strength', 'armour', 'energy_shield', 'evasion'},
},
stackable = {
tables = {'stackables'},
keys = {['Currency'] = true, ['Stackable Currency'] = true, ['Hideout Doodads'] = true, ['Microtransactions'] = true, ['Divination Card'] = true},
args = {'stack_size', 'stack_size_currency_tab', 'description', 'cosmetic_type'},
},
}
core.class_specifics = {
['Amulets'] = {
args = {'is_talisman', 'talisman_tier'},
},
['Life Flasks'] = {
args = {'flask_life'},
},
['Mana Flasks'] = {
args = {'flask_mana'},
},
['Hybrid Flasks'] = {
args = {'flask_life', 'flask_mana'},
},
['Utility Flasks'] = {
tables = {'item_buffs'},
args = {'buff_id', 'buff_values', 'buff_stat_text', 'buff_icon'},
},
['Critical Utility Flasks'] = {
tables = {'item_buffs'},
args = {'buff_id', 'buff_values', 'buff_stat_text', 'buff_icon'},
},
['Active Skill Gems'] = {
args = {'skill_screenshot'},
defaults = {
help_text = i18n.help_text_defaults.active_gem,
size_x = 1,
size_y = 1,
},
frame_type = 'gem',
},
['Support Skill Gems'] = {
args = {'support_gem_letter', 'support_gem_letter_html'},
defaults = {
help_text = i18n.help_text_defaults.support_gem,
size_x = 1,
size_y = 1,
},
frame_type = 'gem',
},
['Shields'] = {
tables = {'shields'},
args = {'block'},
},
['Maps'] = {
tables = {'maps'},
args = {'quality', 'map_tier', 'map_guild_character', 'map_area_id', 'map_area_level', 'unique_map_area_id', 'unique_map_area_level', 'unique_map_guild_character'},
skip_stat_lines = i18n.stat_skip_patterns.maps,
},
['Currency'] = {
frame_type = 'currency',
},
['Stackable Currency'] = {
args = {'is_essence', 'essence_level_restriction', 'essence_level'},
frame_type = 'currency',
},
['Microtransactions'] = {
frame_type = 'currency',
},
['Hideout Doodads'] = {
tables = {'hideout_doodads'},
args = {'is_master_doodad', 'master', 'master_level_requirement', 'master_favour_cost', 'variation_count'},
defaults = {
help_text = i18n.help_text_defaults.hideout_doodad,
},
frame_type = 'currency',
},
['Jewel'] = {
late_args = {'item_limit', 'jewel_radius_html'},
defaults = {
help_text = i18n.help_text_defaults.jewel,
},
skip_stat_lines = i18n.stat_skip_patterns.jewels,
},
['Abyss Jewel'] = {
late_args = {'item_limit', 'jewel_radius_html'},
skip_stat_lines = i18n.stat_skip_patterns.jewels,
},
['Quest Items'] = {
args = {'description'},
frame_type = 'quest',
},
['Divination Card'] = {
tables = {'divination_cards'},
args = {'card_art',},
frame_type = 'divicard',
},
['Labyrinth Item'] = {
frame_type = 'currency',
},
['Labyrinth Trinket'] = {
args = {tables_assoc, 'buff_icon'},
frame_type = 'currency',
},
['Pantheon Soul'] = {
defaults = {
cannot_be_traded_or_modified = true,
},
},
}
-- add defaults from class specifics and class groups
core.item_classes = {}
core.item_classes_extend = {'tables', 'args', 'late_args'}
function core.build_item_classes(tpl_args, frame)
core.map.class.func(tpl_args, frame)
-- Skip building for anything but the specified class.
for _, data in ipairs(m_game.constants.item.class) do
if data['full'] == tpl_args.class then
core.item_classes[data['full']] = {
tables = xtable:new(),
args = xtable:new(),
late_args = xtable:new(),
defaults = {},
}
core.item_classes[data['full']].tables:insertT(core.tables)
break
end
end
for _, row in pairs(core.class_groups) do
for class, _ in pairs(row.keys) do
if class == tpl_args.class then
for _, k in ipairs(core.item_classes_extend) do
if row[k] ~= nil then
core.item_classes[class][k]:insertT(row[k])
end
end
break
end
end
end
local class_specifics = core.class_specifics[tpl_args.class]
if class_specifics then
for _, k in ipairs(core.item_classes_extend) do
if class_specifics[k] ~= nil then
core.item_classes[tpl_args.class][k]:insertT(class_specifics[k])
end
end
if class_specifics.defaults ~= nil then
for key, value in pairs(class_specifics.defaults) do
core.item_classes[tpl_args.class].defaults[key] = value
end
end
end
end
-- GroupTable -> RowTable -> formatter function
--
--
--
-- Contents here are meant to resemble the ingame infobox of items
--
core.item_display_groups = {
-- Tags, stats, level, etc
{
{
args = {'cosmetic_type'},
func = core.factory.display_value{
options = {
[1] = {
key = 'cosmetic_type',
fmt = '%s',
color = 'default'
},
},
},
},
{
args = function(tpl_args, frame)
if tpl_args.class == nil then
return false
end
return core.class_groups.weapons.keys[tpl_args.class] ~= nil
end,
func = core.factory.display_value{
options = {
[1] = {
key = 'class',
color = 'default',
},
},
},
},
{
args = {'gem_tags'},
func = function(tpl_args, frame)
local out = {}
for i, tag in ipairs(tpl_args.gem_tags) do
out[#out+1] = string.format(i18n.gem_tag_category, tag, tag)
end
return table.concat(out, ', ')
end,
},
{
args = {'support_gem_letter_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'support_gem_letter_html',
inline = i18n.tooltips.support_icon,
},
},
},
},
{
args = {'radius'},
func = core.factory.display_value{
options = {
[1] = {
key = 'radius',
inline = i18n.tooltips.radius,
func = core.factory.descriptor_value{key='radius_description'},
},
[2] = {
key = 'radius_secondary',
inline = ' / %s',
func = core.factory.descriptor_value{key='radius_secondary_description'},
},
[3] = {
key = 'radius_tertiary',
inline = ' / %s',
func = core.factory.descriptor_value{key='radius_tertiary_description'},
},
},
},
},
-- TODO: gem level here. Maybe put max level here?
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'mana_cost',
hide_default = 100,
fmt = function (tpl_args, frame)
if tpl_args.has_percentage_mana_cost then
return '%i%%'
else
return '%i'
end
end,
inline = function (tpl_args, frame)
if tpl_args.has_reservation_mana_cost then
return i18n.tooltips.mana_reserved
else
return i18n.tooltips.mana_cost
end
end,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'mana_multiplier',
hide_default = 100,
fmt = '%i%%',
inline = i18n.tooltips.mana_multiplier,
},
},
},
},
-- TODO: i18n
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'vaal_souls_requirement',
hide_default = 0,
fmt = '%i (N) / ',
inline = i18n.tooltips.vaal_souls_per_use,
},
[2] = {
key = 'vaal_souls_requirement',
hide_default = 0,
fmt = '%i (C) / ',
func = function (tpl_args, frame, value)
return value*1.5
end,
},
[3] = {
key = 'vaal_souls_requirement',
hide_default = 0,
fmt = '%i (M)',
func = function (tpl_args, frame, value)
return value*2
end,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'vaal_stored_uses',
hide_default = 0,
fmt = '%i',
inline = i18n.tooltips.stored_uses,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'stored_uses',
hide_default = 0,
fmt = '%i',
inline = i18n.tooltips.stored_uses,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'cooldown',
hide_default = 0,
fmt = '%.2f sec',
inline = i18n.tooltips.cooldown_time,
},
},
},
},
{
args = {'cast_time'},
func = core.factory.display_value{
options = {
[1] = {
key = 'cast_time',
hide_default = 0,
fmt = '%.2f sec',
inline = i18n.tooltips.cast_time,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'critical_strike_chance',
hide_default = 0,
fmt = '%.2f%%',
inline = i18n.tooltips.critical_strike_chance,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type='gem',
options = {
[1] = {
key = 'damage_effectiveness',
hide_default = 100,
fmt = '%i%%',
inline = i18n.tooltips.damage_effectiveness,
},
},
},
},
{
args = {'projectile_speed'},
func = core.factory.display_value{
options = {
[1] = {
key = 'projectile_speed',
inline = i18n.tooltips.projectile_speed,
},
},
},
},
-- Quality is before item stats, but after gem stuff and item class
{
args = {'quality'},
func = core.factory.display_value{
options = {
[1] = {
key = 'quality',
fmt = '+%i%%',
color = 'mod',
inline = i18n.tooltips.quality,
hide_default = 0,
},
},
},
},
-- Weapon only
{
args = {'physical_damage_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'physical_damage_html',
fmt = '%s',
inline = i18n.tooltips.physical_damage,
},
},
},
},
{
args = nil,
func = function(tpl_args, frame)
local text = ''
for _, dtype in ipairs({'fire_damage_html', 'cold_damage_html', 'lightning_damage_html'}) do
local value = tpl_args[dtype]
if value ~= nil then
text = text .. ' ' .. value
end
end
if text ~= '' then
return string.format(i18n.tooltips.elemental_damage, text)
else
return
end
end,
},
{
args = {'chaos_damage_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'chaos_damage_html',
fmt = '%s',
inline = i18n.tooltips.chaos_damage,
},
},
},
},
{
args = {'critical_strike_chance_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'critical_strike_chance_html',
fmt = '%s',
inline = i18n.tooltips.critical_strike_chance,
},
},
},
},
{
args = {'attack_speed_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'attack_speed_html',
fmt = '%s',
inline = i18n.tooltips.attacks_per_second,
},
},
},
},
{
args = {'range_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'range_html',
fmt = '%s',
inline = i18n.tooltips.weapon_range,
},
},
},
},
-- Map only
{
args = {'map_area_level'},
func = core.factory.display_value{
options = {
[1] = {
key = 'map_area_level',
fmt = '%i',
inline = i18n.tooltips.map_level,
},
},
},
},
{
args = {'map_tier'},
func = core.factory.display_value{
options = {
[1] = {
key = 'map_tier',
fmt = '%i',
inline = i18n.tooltips.map_tier,
},
},
},
},
{
args = function(tpl_args, frame)
return tpl_args.map_guild_character ~= nil and tpl_args.rarity == 'Normal'
end,
func = core.factory.display_value{
options = {
[1] = {
key = 'map_guild_character',
fmt = '%s',
inline = i18n.tooltips.map_guild_character,
},
},
},
},
{
args = function(tpl_args, frame)
return tpl_args.unique_map_guild_character ~= nil and tpl_args.rarity == 'Unique'
end,
func = core.factory.display_value{
options = {
[1] = {
key = 'unique_map_guild_character',
fmt = '%s',
inline = i18n.tooltips.map_guild_character,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type = 'stat',
options = {
[1] = {
key = 'map_item_drop_quantity_+%',
fmt = '+%i%%',
color = 'mod',
inline = i18n.tooltips.item_quantity,
hide_default = 0,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type = 'stat',
options = {
[1] = {
key = 'map_item_drop_rarity_+%',
fmt = '+%i%%',
color = 'mod',
inline = i18n.tooltips.item_rarity,
hide_default = 0,
},
},
},
},
{
args = nil,
func = core.factory.display_value{
type = 'stat',
options = {
[1] = {
key = 'map_pack_size_+%',
fmt = '+%i%%',
color = 'mod',
inline = i18n.tooltips.monster_pack_size,
hide_default = 0,
},
},
},
},
-- Jewel Only
{
args = nil,
func = core.factory.display_value{
options = {
[1] = {
key = 'item_limit',
fmt = '%i',
inline = i18n.tooltips.limited_to,
},
},
},
},
{
args = {'jewel_radius_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'jewel_radius_html',
fmt = '%s',
inline = i18n.tooltips.radius,
},
},
},
},
-- Flask only
{
args = {'flask_mana_html', 'flask_duration_html'},
--func = core.factory.display_flask('flask_mana'),
func = core.factory.display_value{
inline = i18n.tooltips.flask_mana_recovery,
options = {
[1] = {
key = 'flask_mana_html',
fmt = '%s',
},
[2] = {
key = 'flask_duration_html',
fmt = '%s',
},
}
},
},
{
args = {'flask_life_html', 'flask_duration_html'},
func = core.factory.display_value{
inline = i18n.tooltips.flask_life_recovery,
options = {
[1] = {
key = 'flask_life_html',
fmt = '%s',
},
[2] = {
key = 'flask_duration_html',
fmt = '%s',
},
}
},
},
{
-- don't display for mana/life flasks
args = function(tpl_args, frame)
for _, k in ipairs({'flask_life_html', 'flask_mana_html'}) do
if tpl_args[k] ~= nil then
return false
end
end
return tpl_args['flask_duration_html'] ~= nil
end,
func = core.factory.display_value{
inline = i18n.tooltips.flask_duration,
options = {
[1] = {
key = 'flask_duration_html',
fmt = '%s',
},
},
},
},
{
args = {'charges_per_use_html', 'charges_max_html'},
func = core.factory.display_value{
inline = i18n.tooltips.flask_charges_per_use,
options = {
[1] = {
key = 'charges_per_use_html',
fmt = '%s',
},
[2] = {
key = 'charges_max_html',
fmt = '%s',
},
},
},
},
{
args = {'buff_stat_text'},
func = core.factory.display_value{
options = {
[1] = {
key = 'buff_stat_text',
color = 'mod',
},
},
},
},
-- armor
{
args = {'block_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'block_html',
inline = i18n.tooltips.chance_to_block,
fmt = '%s',
hide_default = 0,
hide_default_key = 'block',
},
},
},
},
{
args = {'armour_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'armour_html',
inline = i18n.tooltips.armour,
fmt = '%s',
hide_default = 0,
hide_default_key = 'armour',
},
},
},
},
{
args = {'evasion_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'evasion_html',
inline = i18n.tooltips.evasion,
fmt = '%s',
hide_default = 0,
hide_default_key = 'evasion',
},
},
},
},
{
args = {'energy_shield_html'},
func = core.factory.display_value{
options = {
[1] = {
key = 'energy_shield_html',
inline = i18n.tooltips.energy_shield,
fmt = '%s',
hide_default = 0,
hide_default_key = 'energy_shield',
},
},
},
},
-- Amulet only
{
args = {'talisman_tier'},
func = core.factory.display_value{
options = {
[1] = {
key = 'talisman_tier',
fmt = '%i',
inline = i18n.tooltips.talisman_tier,
},
},
},
},
-- Misc
{
args = {'stack_size'},
func = core.factory.display_value{
options = {
[1] = {
key = 'stack_size',
hide_default = 1,
fmt = '%i',
inline = i18n.tooltips.stack_size,
},
},
},
},
-- Essence stuff
{
args = {'essence_level'},
func = core.factory.display_value{
options = {
[1] = {
key = 'essence_level',
fmt = '%i',
inline = i18n.tooltips.essence_level,
},
},
},
},
},
-- Requirements
{
-- TODO: i18n Master name?
{
args = {'master', 'master_level_requirement'},
func = function(tpl_args, frame)
-- masters have been validated before
local data
for i, rowdata in ipairs(m_game.constants.masters) do
if tpl_args.master == rowdata.full then
data = rowdata
break
end
end
return m_util.html.poe_color('default', i18n.tooltips.requires, string.format('[[%s|%s %s]]', data.full, data.short_upper, tpl_args.master_level_requirement))
end
},
-- Instead of item level, show drop level if any
{
args = nil,
func = function(tpl_args, frame)
local opt = {
[1] = {
key = 'required_level_final_html',
hide_default = 1,
hide_default_key = 'required_level_final',
inline = i18n.tooltips.level_inline,
inline_color = false,
},
}
for _, attr in ipairs(m_game.constants.attributes) do
opt[#opt+1] = {
key = string.format('required_%s_html', attr['long_lower']),
hide_default = 0,
hide_default_key = string.format('required_%s', attr['long_lower']),
inline = ', %s ' .. attr['short_upper'],
inline_color = false,
}
end
local requirements = core.factory.display_value{options = opt}(tpl_args, frame)
-- return early
if requirements == nil then
return
end
requirements = string.gsub(requirements, '^, ', '')
return m_util.html.poe_color('default', string.format(i18n.tooltips.requires, requirements))
end,
},
},
-- Gem description
{
css_class = '-textwrap tc -gemdesc',
{
args = {'gem_description'},
func = core.factory.display_value_only('gem_description'),
},
},
-- Gem Quality Stats
{
css_class = '-textwrap tc -mod',
{
args = {'quality_stat_text'},
func = function(tpl_args, frame)
lines = {}
lines[#lines+1] = m_util.html.poe_color('default', i18n.tooltips.gem_quality)
lines[#lines+1] = tpl_args.quality_stat_text
return table.concat(lines, '<br>')
end,
},
},
-- Gem Implicit Stats
{
css_class = '-textwrap tc -mod',
{
args = function(tpl_args, frame)
return core.class_groups.gems.keys[tpl_args.class] and tpl_args.stat_text
end,
func = function(tpl_args, frame)
lines = {}
lines[#lines+1] = tpl_args.stat_text
if tpl_args.gem_tags:contains('Vaal') then
lines[#lines+1] = m_util.html.poe_color('corrupted', i18n.tooltips.corrupted)
end
return table.concat(lines, '<br>')
end,
},
},
-- Implicit Stats
{
css_class = '-textwrap tc -mod',
func = function(tpl_args, frame)
if tpl_args.implicit_stat_text ~= '' then
return {tpl_args.implicit_stat_text}
else
return {}
end
end,
},
-- Stats
{
css_class = '-textwrap tc -mod',
func = function(tpl_args, frame)
if tpl_args.explicit_stat_text ~= '' then
return {tpl_args.explicit_stat_text}
else
return {}
end
end,
},
-- Experience
--[[{
{
args = {'experience'},
func = core.factory.display_value{
key = 'experience',
options = {
[1] = {
fmt = '%i',
},
},
},
},
},]]--
-- Description (currency, doodads)
{
css_class = '-textwrap tc -mod',
{
args = {'description'},
func = core.factory.display_value_only('description'),
},
},
-- Variations (for doodads)
{
css_class = 'tc -mod',
{
args = {'variation_count'},
func = function(tpl_args, frame)
local txt
if tpl_args.variation_count == 1 then
txt = i18n.tooltips.variation_singular
else
txt = i18n.tooltips.variation_plural
end
return string.format('%i %s', tpl_args.variation_count, txt)
end,
},
},
-- Flavour Text
{
css_class = '-textwrap tc -flavour',
{
args = {'flavour_text'},
func = core.factory.display_value_only('flavour_text'),
},
},
-- Prophecy text
{
css_class = '-textwrap tc -value',
{
args = {'prediction_text'},
func = core.factory.display_value_only('prediction_text'),
},
},
-- Can not be traded or modified
{
css_class = '-textwrap tc -canttradeormodify',
{
args = {'cannot_be_traded_or_modified'},
func = function(tpl_args, frame)
if tpl_args.cannot_be_traded_or_modified == true then
return i18n.tooltips.cannot_be_traded_or_modified
end
end,
},
},
-- Help text
{
css_class = '-textwrap tc -help',
{
args = {'help_text'},
func = core.factory.display_value_only('help_text'),
},
},
-- Cost (i.e. vendor costs)
{
--css_class = '',
{
args = {'master_favour_cost'},
func = core.factory.display_value{
options = {
[1] = {
key = 'master_favour_cost',
inline = i18n.tooltips.favour_cost,
color = 'currency',
},
},
},
},
{
args = {'seal_cost'},
func = core.factory.display_value{
options = {
[1] = {
key = 'seal_cost',
fmt = '%dx ',
color = 'currency',
inline = function (tpl_args, frame)
return i18n.tooltips.seal_cost .. f_item_link{item_name_exact='Silver Coin', html=''}
end,
},
},
},
},
},
}
--
-- This is meant to show additional information about the item in a separate infobox
--
core.extra_display_groups = {
-- Drop info
{
header = i18n.tooltips.drop_restrictions,
{
args = {'drop_enabled'},
func = core.factory.display_value{
options = {
[1] = {
key = 'drop_level',
fmt = '%i',
inline = i18n.tooltips.level,
},
[2] = {
key = 'drop_level_maximum',
hide_default = 100,
fmt = '%i',
inline = ' / %s',
},
},
},
},
{
args = {'drop_leagues'},
func = function(tpl_args, frame)
return string.format(i18n.tooltips.league_restriction, m_util.html.poe_color('value', table.concat(tpl_args.drop_leagues, ', ')))
end
},
{
args = {'drop_areas_html'},
func = core.factory.display_value_only('drop_areas_html'),
},
{
args = {'drop_text'},
func = core.factory.display_value_only('drop_text'),
},
{
args = function(tpl_args, frame)
if tpl_args.drop_enabled == true then
return false
end
return true
end,
func = function(tpl_args, frame)
local span = mw.html.create('span')
span
:attr('class', 'infobox-disabled-drop')
:wikitext(i18n.tooltips.drop_disabled)
:done()
return tostring(span)
end,
},
},
{
header = i18n.tooltips.purchase_costs,
{
args = function(tpl_args, frame)
for rarity, data in pairs(tpl_args.purchase_costs) do
if #data > 0 then
return true
end
end
return false
end,
func = function(tpl_args, frame)
local tbl = mw.html.create('table')
tbl
--:attr('class', 'wikitable')
:attr('style', 'width: 100%; margin-top: 0px;')
for _, rarity_names in ipairs(m_game.constants.item.rarity) do
local data = tpl_args.purchase_costs[rarity_names.long_lower]
if #data > 0 then
local tr = tbl:tag('tr')
tr
:tag('td')
:wikitext(rarity_names.long_upper)
local td = tr:tag('td')
for _, purchase_data in ipairs(data) do
td:wikitext(string.format('%dx [[%s]]<br />', purchase_data.amount, purchase_data.name))
end
end
end
return tostring(tbl)
end,
},
},
{
header = i18n.tooltips.sell_price,
{
args = {'sell_price_order'},
func = function(tpl_args, frame)
local out = {}
for _, item_name in ipairs(tpl_args.sell_price_order) do
out[#out+1] = string.format('%dx [[%s]]', tpl_args.sell_prices[item_name], item_name)
end
return table.concat(out, '<br />')
end,
},
},
-- Damage per second
{
header = i18n.tooltips.damage_per_second,
-- Autoinsert here from dps map
},
}
for i, data in ipairs(core.dps_map) do
table.insert(core.extra_display_groups[4], {
args = {data.name .. '_html'},
func = core.factory.display_value{
options = {
[1] = {
key = data.name .. '_html',
inline = data.label_infobox .. ': %s',
fmt = '%s',
-- the html already contains the colour
no_color = true,
},
},
},
})
if i == 5 then
table.insert(core.extra_display_groups[4], {
args = function (tpl_args, frame)
return tpl_args.elemental_dps_html ~= nil or tpl_args.poison_dps_html ~= nil
end,
func = function (tpl_args, frame)
return ''
end,
})
elseif i == 7 then
table.insert(core.extra_display_groups[4], {
args = {'dps_html'},
func = function (tpl_args, frame)
return ''
end,
})
end
end
core.result = {}
-- for sort type see:
-- https://meta.wikimedia.org/wiki/Help:Sorting
core.result.generic_item = {
{
arg = 'base_item',
header = i18n.item_table.base_item,
fields = {'items.base_item', 'items.base_item_page'},
display = function(tr, data)
tr
:tag('td')
:attr('data-sort-value', data['items.base_item'])
:wikitext(string.format('[[%s|%s]]', data['items.base_item_page'], data['items.base_item']))
end,
order = 1000,
sort_type = 'text',
},
{
arg = 'class',
header = i18n.item_table.item_class,
fields = {'items.class'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 1001,
sort_type = 'text',
},
{
arg = 'essence',
header = i18n.item_table.essence_level,
fields = {'essences.level'},
display = h.tbl.display.factory.value{},
order = 2000,
},
{
arg = {'drop', 'drop_level'},
header = i18n.item_table.drop_level,
fields = {'items.drop_level'},
display = h.tbl.display.factory.value{},
order = 3000,
},
{
arg = 'stack_size',
header = i18n.item_table.stack_size,
fields = {'stackables.stack_size'},
display = h.tbl.display.factory.value{},
order = 4000,
},
{
arg = 'stack_size_currency_tab',
header = i18n.item_table.stack_size_currency_tab,
fields = {'stackables.stack_size_currency_tab'},
display = h.tbl.display.factory.value{},
order = 4001,
},
{
arg = 'level',
header = m_game.level_requirement.icon,
fields = h.tbl.range_fields('items.required_level'),
display = h.tbl.display.factory.range{field='items.required_level'},
order = 5000,
},
{
arg = 'ar',
header = i18n.item_table.armour,
fields = h.tbl.range_fields('armours.armour'),
display = h.tbl.display.factory.range{field='armours.armour'},
order = 6000,
},
{
arg = 'ev',
header =i18n.item_table.evasion,
fields = h.tbl.range_fields('armours.evasion'),
display = h.tbl.display.factory.range{field='armours.evasion'},
order = 6001,
},
{
arg = 'es',
header = i18n.item_table.energy_shield,
fields = h.tbl.range_fields('armours.energy_shield'),
display = h.tbl.display.factory.range{field='armours.energy_shield'},
order = 6002,
},
{
arg = 'block',
header = i18n.item_table.block,
fields = h.tbl.range_fields('shields.block'),
display = h.tbl.display.factory.range{field='shields.block'},
order = 6003,
},
--[[{
arg = 'physical_damage_min',
header = m_util.html.abbr('Min', 'Local minimum weapon damage'),
fields = h.tbl.range_fields('minimum physical damage'),
display = h.tbl.display.factory.range{field='minimum physical damage'},
order = 7000,
},
{
arg = 'physical_damage_max',
header = m_util.html.abbr('Max', 'Local maximum weapon damage'),
fields = h.tbl.range_fields('maximum physical damage'),
display = h.tbl.display.factory.range{field='maximum physical damage'},
order = 7001,
},]]--
{
arg = {'weapon', 'damage'},
header = i18n.item_table.damage,
fields = {'weapons.damage_html', 'weapons.damage_avg'},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['weapons.damage_avg'])
:wikitext(data['weapons.damage_html'])
end,
order = 8000,
},
{
arg = {'weapon', 'aps'},
header = i18n.item_table.attacks_per_second,
fields = h.tbl.range_fields('weapons.attack_speed'),
display = h.tbl.display.factory.range{field='weapons.attack_speed'},
order = 8001,
},
{
arg = {'weapon', 'crit'},
header = i18n.item_table.local_critical_strike_chance,
fields = h.tbl.range_fields('weapons.critical_strike_chance'),
display = h.tbl.display.factory.range{field='weapons.critical_strike_chance'},
order = 8002,
},
{
arg = 'flask_life',
header = i18n.item_table.flask_life,
fields = h.tbl.range_fields('flasks.life'),
display = h.tbl.display.factory.range{field='flasks.life'},
order = 9000,
},
{
arg = 'flask_mana',
header = i18n.item_table.flask_mana,
fields = h.tbl.range_fields('flasks.mana'),
display = h.tbl.display.factory.range{field='flasks.mana'},
order = 9001,
},
{
arg = 'flask',
header = i18n.item_table.flask_duration,
fields = h.tbl.range_fields('flasks.duration'),
display = h.tbl.display.factory.range{field='flasks.duration'},
order = 9002,
},
{
arg = 'flask',
header = i18n.item_table.flask_charges_per_use,
fields = h.tbl.range_fields('flasks.charges_per_use'),
display = h.tbl.display.factory.range{field='flasks.charges_per_use'},
order = 9003,
},
{
arg = 'flask',
header = i18n.item_table.flask_maximum_charges,
fields = h.tbl.range_fields('flasks.charges_max'),
display = h.tbl.display.factory.range{field='flasks.charges_max'},
order = 9004,
},
{
arg = 'item_limit',
header = i18n.item_table.item_limit,
fields = {'jewels.item_limit'},
display = h.tbl.display.factory.value{},
order = 10000,
},
{
arg = 'jewel_radius',
header = i18n.item_table.jewel_radius,
fields = {'jewels.radius_html'},
display = function (tr, data)
tr
:tag('td')
:wikitext(data['jewels.radius_html'])
end,
order = 10001,
},
{
arg = 'map_tier',
header = i18n.item_table.map_tier,
fields = {'maps.tier'},
display = h.tbl.display.factory.value{},
order = 11000,
},
{
arg = 'map_level',
header = i18n.item_table.map_level,
fields = {'maps.area_level'},
display = h.tbl.display.factory.value{},
order = 11010,
},
{
arg = 'map_guild_character',
header = i18n.item_table.map_guild_character,
fields = {'maps.guild_character'},
display = h.tbl.display.factory.value{colour='value'},
order = 11020,
sort_type = 'text',
},
{
arg = 'buff',
header = i18n.item_table.buff_effects,
fields = {'item_buffs.stat_text'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12000,
sort_type = 'text',
},
{
arg = 'stat',
header = i18n.item_table.stats,
fields = {'items.stat_text'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12001,
sort_type = 'text',
},
{
arg = 'description',
header = i18n.item_table.effects,
fields = {'items.description'},
display = h.tbl.display.factory.value{colour='mod'},
order = 12002,
sort_type = 'text',
},
{
arg = 'flavour_text',
header = i18n.item_table.flavour_text,
fields = {'items.flavour_text'},
display = h.tbl.display.factory.value{colour='flavour'},
order = 12003,
sort_type = 'text',
},
{
arg = 'help_text',
header = i18n.item_table.help_text,
fields = {'items.help_text'},
display = h.tbl.display.factory.value{colour='help'},
order = 12005,
sort_type = 'text',
},
{
arg = {'prophecy', 'objective'},
header = i18n.item_table.objective,
fields = {'prophecies.objective'},
display = h.tbl.display.factory.value{},
order = 13002,
},
{
arg = {'prophecy', 'reward'},
header = i18n.item_table.reward,
fields = {'prophecies.reward'},
display = h.tbl.display.factory.value{},
order = 13001,
},
{
arg = {'prophecy', 'seal_cost'},
header = i18n.item_table.seal_cost,
fields = {'prophecies.seal_cost'},
display = h.tbl.display.factory.value{colour='currency'},
order = 13002,
},
{
arg = {'prediction_text'},
header = i18n.item_table.prediction_text,
fields = {'prophecies.prediction_text'},
display = h.tbl.display.factory.value{colour='value'},
order = 12004,
sort_type = 'text',
},
{
arg = 'buff_icon',
header = i18n.item_table.buff_icon,
fields = {'item_buffs.icon'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 14000,
sort_type = 'text',
},
{
arg = {'drop', 'drop_leagues'},
header = i18n.item_table.drop_leagues,
fields = {'items.drop_leagues'},
display = function (tr, data)
tr
:tag('td')
:wikitext(table.concat(m_util.string.split(data['items.drop_leagues'], ','), '<br>'))
end,
order = 15000,
},
{
arg = {'drop', 'drop_areas'},
header = i18n.item_table.drop_areas,
fields = {'items.drop_areas_html'},
display = h.tbl.display.factory.value{},
order = 15001,
},
{
arg = {'drop', 'drop_text'},
header = i18n.item_table.drop_text,
fields = {'items.drop_text'},
display = h.tbl.display.factory.value{},
order = 15002,
},
}
for i, data in ipairs(core.dps_map) do
table.insert(core.result.generic_item, {
arg = data.name,
header = data.label,
fields = h.tbl.range_fields(string.format('weapons.%s', data.field)),
display = h.tbl.display.factory.range{field=string.format('weapons.%s', data.field)},
order = 8100+i,
})
end
core.result.skill_gem_new = {
{
arg = 'icon',
header = i18n.item_table.support_gem_letter,
fields = {'skill_gems.support_gem_letter_html'},
display = h.tbl.display.factory.value{},
order = 1000,
sort_type = 'text',
},
{
arg = 'skill_icon',
header = i18n.item_table.skill_icon,
fields = {'skills3.skill_icon'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='[[%s]]',
},
}},
order = 1001,
sort_type = 'text',
},
{
arg = 'description',
header = i18n.item_table.description,
fields = {'skills3.description'},
display = h.tbl.display.factory.value{},
order = 2000,
sort_type = 'text',
},
{
arg = 'level',
header = m_game.level_requirement.icon,
fields = {'items.level_requirement'},
display = h.tbl.display.factory.value{},
order = 3004,
},
{
arg = 'crit',
header = i18n.item_table.skill_critical_strike_chance,
fields = {'skill_levels.critical_strike_chance'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
},
}},
order = 4000,
},
{
arg = 'cast_time',
header = i18n.item_table.cast_time,
fields = {'skill_levels.cast_time'},
display = h.tbl.display.factory.value{},
order = 4001,
},
{
arg = 'dmgeff',
header = i18n.item_table.damage_effectiveness,
fields = {'skill_levels.damage_effectiveness'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
},
}},
order = 4002,
},
{
arg = 'mcm',
header = i18n.item_table.mana_cost_multiplier,
fields = {'skill_levels.mana_multiplier'},
display = h.tbl.display.factory.value{options = {
[1] = {
fmt='%s%%',
},
}},
order = 5000,
},
{
arg = 'mana',
header = i18n.item_table.mana_cost,
fields = {'skill_levels.mana_cost', 'skills3.has_percentage_mana_cost', 'skills3.has_reservation_mana_cost'},
display = function (tr, data)
local appendix = ''
if m_util.cast.boolean(data['skills3.has_percentage_mana_cost']) then
appendix = appendix .. '%'
end
if m_util.cast.boolean(data['skills3.has_reservation_mana_cost']) then
appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix
end
tr
:tag('td')
:attr('data-sort-value', data['skill_levels.mana_cost'])
:wikitext(string.format('%d', data['skill_levels.mana_cost']) .. appendix)
end,
order = 5001,
},
{
arg = 'vaal',
header = i18n.item_table.vaal_souls_requirement,
fields = {'skill_levels.vaal_souls_requirement'},
display = function (tr, data)
local souls = tonumber(data['skill_levels.vaal_souls_requirement'])
tr
:tag('td')
:attr('data-sort-value', souls)
:wikitext(string.format('%d / %d / %d', souls, souls*1.5, souls*2))
end,
order = 6000,
},
{
arg = 'vaal',
header = i18n.item_table.stored_uses,
fields = {'skill_levels.vaal_stored_uses'},
display = h.tbl.display.factory.value{},
order = 6001,
},
{
arg = 'radius',
header = i18n.item_table.primary_radius,
fields = {'skills3.radius', 'skills3.radius_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skills3.radius'])
:wikitext(core.factory.descriptor_value{tbl=data, key='skills3.radius_description'}(nil, nil, data['skills3.radius']))
end,
order = 7000,
},
{
arg = 'radius',
header = i18n.item_table.secondary_radius,
fields = {'skills3.radius_secondary', 'skills3.radius_secondary_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skills3.radius_secondary'])
:wikitext(core.factory.descriptor_value{tbl=data, key='skills3.radius_secondary_description'}(nil, nil, data['skills3.radius_secondary']))
end,
order = 7001,
},
{
arg = 'radius',
header = i18n.item_table.tertiary_radius,
fields = {'skills3.radius_tertiary', 'skills3.radius_tertiary_description'},
options = {[2] = {optional = true}},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data['skills3.radius_tertiary'])
:wikitext(core.factory.descriptor_value{tbl=data, key='skills3.radius_tertiary_description'}(nil, nil, data['skills3.radius_tertiary']))
end,
order = 7002,
},
}
for i, attr in ipairs(m_game.constants.attributes) do
table.insert(core.result.generic_item, 7, {
arg = attr.short_lower,
header = attr.icon,
fields = h.tbl.range_fields(string.format('items.required_%s', attr.long_lower)),
display = h.tbl.display.factory.range{field=string.format('items.required_%s', attr.long_lower)},
order = 5000+i,
})
table.insert(core.result.skill_gem_new, 1, {
arg = attr.short_lower,
header = attr.icon,
fields = {string.format('skill_gems.%s_percent', attr.long_lower)},
display = function (tr, data)
tr
:tag('td')
:attr('data-sort-value', data[string.format('skill_gems.%s_percent', attr.long_lower)])
:wikitext('[[File:Yes.png|yes|link=]]')
end,
order = 3000+i,
})
end
-- ----------------------------------------------------------------------------
-- Tables
-- ----------------------------------------------------------------------------
function cargo_declare(data)
return function(frame)
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
local dcl_args = {}
dcl_args._table = data.table
for k, field_data in pairs(data.fields) do
if field_data.field then
dcl_args[field_data.field] = field_data.type
for _, stat_data in pairs(core.stat_map) do
if stat_data.field == k then
for _, range_fields in ipairs(h.range_fields) do
dcl_args[stat_data.field .. range_fields.field] = range_fields.type
end
break
end
end
end
end
if dcl_args._table == 'weapons' then
for _, dps_data in ipairs(core.dps_map) do
for _, range_fields in ipairs(h.range_fields) do
dcl_args[dps_data.field .. range_fields.field] = range_fields.type
end
end
end
if tpl_args.debug then
mw.logObject(dcl_args)
end
return m_util.cargo.declare(frame, dcl_args)
end
end
p.table_items = cargo_declare(core.cargo.items)
p.table_item_purchase_costs = cargo_declare(core.cargo.item_purchase_costs)
p.table_item_stats = cargo_declare(core.cargo.item_stats)
p.table_item_buffs = cargo_declare(core.cargo.item_buffs)
p.table_upgraded_from_sets = cargo_declare(core.cargo.upgraded_from_sets)
p.table_upgraded_from_groups = cargo_declare(core.cargo.upgraded_from_groups)
p.table_amulets = cargo_declare(core.cargo.amulets)
p.table_flasks = cargo_declare(core.cargo.flasks)
p.table_weapons = cargo_declare(core.cargo.weapons)
p.table_armours = cargo_declare(core.cargo.armours)
p.table_shields = cargo_declare(core.cargo.shields)
p.table_skill_gems = cargo_declare(core.cargo.skill_gems)
p.table_maps = cargo_declare(core.cargo.maps)
p.table_stackables = cargo_declare(core.cargo.stackables)
p.table_essences = cargo_declare(core.cargo.essences)
p.table_hideout_doodads = cargo_declare(core.cargo.hideout_doodads)
p.table_prophecies = cargo_declare(core.cargo.prophecies)
p.table_divination_cards = cargo_declare(core.cargo.divination_cards)
p.table_jewels = cargo_declare(core.cargo.jewels)
-- ----------------------------------------------------------------------------
-- Page views
-- ----------------------------------------------------------------------------
--
-- Template:Item
--
function p.itembox (frame)
--
-- Args/Frame
--
local t = os.clock()
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
--
-- Shared args
--
tpl_args._flags = {}
tpl_args._base_item_args = {}
tpl_args._mods = {}
tpl_args._stats = {}
tpl_args._implicit_stats = {}
tpl_args._explicit_stats = {}
tpl_args._subobjects = {}
tpl_args._properties = {}
tpl_args._errors = {}
core.build_item_classes(tpl_args, frame)
core.build_cargo_data(tpl_args, frame)
-- Using general purpose function to handle release and removal versions
m_util.args.version(tpl_args, {frame=frame, set_properties=true})
-- Must validate some argument early. It is required for future things
core.process_arguments(tpl_args, frame, {array=core.default_args})
core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].args})
-- Base Item
core.process_base_item(tpl_args, frame)
-- Prophecy special snowflake
if tpl_args.base_item == 'Prophecy' then
err = core.process_arguments(tpl_args, frame, {array=core.prophecy_args})
if err then
return err
end
tpl_args.inventory_icon = string.format(i18n.inventory_icon, 'Prophecy')
end
-- Mods
for _, k in ipairs({'implicit', 'explicit'}) do
local success = true
local i = 1
while success do
success = core.validate_mod(tpl_args, frame, {key=k, i=i})
i = i + 1
end
end
core.process_smw_mods(tpl_args, frame)
-- Add stats - this is for when mods are not set, but we still need stats to calcuate new armour values etc
m_util.args.stats(tpl_args, {prefix='extra_'})
for _, stat in ipairs(tpl_args.extra_stats) do
if stat.value ~= nil then
stat.min = stat.value
stat.max = stat.value
stat.avg = stat.value
end
h.stats_update(tpl_args, stat.id, stat, nil, '_stats')
h.stats_update(tpl_args, stat.id, stat, nil, '_explicit_stats')
end
-- Transpose stats into cargo data
for id, data in pairs(tpl_args._stats) do
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_stats',
id = id,
min = data.min,
max = data.max,
avg = data.avg,
is_implicit = nil,
}
end
for id, data in pairs(tpl_args._explicit_stats) do
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_stats',
id = id,
min = data.min,
max = data.max,
avg = data.avg,
is_implicit = false,
}
end
for id, data in pairs(tpl_args._implicit_stats) do
tpl_args._subobjects[#tpl_args._subobjects+1] = {
_table = 'item_stats',
id = id,
min = data.min,
max = data.max,
avg = data.avg,
is_implicit = true,
}
end
-- Handle extra stats (for gems)
if core.class_groups.gems.keys[tpl_args.class] then
m_skill.skill(frame, tpl_args)
end
--
-- Handle local stats increases/reductions/additions
--
local skip = {}
-- general stats
for k, data in pairs(core.stat_map) do
local value = tpl_args[k]
if value ~= nil and skip[k] == nil then
value = {min=value, max=value, base=value}
-- If stats are overriden we scan save some CPU time here
local overridden = false
if data.stats_override ~= nil then
for stat_id, override_value in pairs(data.stats_override) do
local stat_value = tpl_args._stats[stat_id]
if stat_value ~= nil then
-- Use the value of stat
if override_value == true then
value.min = stat_value.min
value.max = stat_value.max
overridden = true
elseif stat_value ~= 0 then
value.min = override_value.min
value.max = override_value.max
overridden = true
end
end
end
end
if overridden == false then
-- The simple cases; this must be using ipairs as "add" must apply before
for _, operator in ipairs({'add', 'more'}) do
local st = data['stats_' .. operator]
if st ~= nil then
for _, statid in ipairs(st) do
if tpl_args._stats[statid] ~= nil then
h.stat[operator](value, tpl_args._stats[statid])
end
end
end
end
-- For increased stats we need to add them up first
for stat_key, stat_func in pairs({increased=h.stat.more, increased_inverse=h.stat.more_inverse}) do
local st = data['stats_' .. stat_key]
if st ~= nil then
local total_increase = {min=0, max=0}
for _, statid in ipairs(st) do
if tpl_args._stats[statid] ~= nil then
for var, current_value in pairs(total_increase) do
total_increase[var] = current_value + tpl_args._stats[statid][var]
end
end
end
stat_func(value, total_increase)
end
end
if data.minimum ~= nil then
for _, key in ipairs({'min', 'max'}) do
if value[key] < data.minimum then
value[key] = data.minimum
end
end
end
else
end
value.avg = (value.min + value.max) / 2
-- don't add the properties unless we need to
if (data.default ~= nil and (value.min ~= data.default or value.max ~= data.default)) or data.default == nil then
for short_key, range_data in pairs(h.range_map) do
tpl_args[data.field .. range_data.var] = value[short_key]
end
-- process to HTML to use on list pages or other purposes
h.handle_range_args(tpl_args, frame, k, data.field, value, data.html_fmt_options or {})
end
for short_key, range_data in pairs(h.range_map) do
tpl_args[k .. range_data.var] = value[short_key]
end
end
end
-- calculate and handle weapon dps
if core.class_groups.weapons.keys[tpl_args.class] then
for _, data in ipairs(core.dps_map) do
local damage = {
min = {},
max = {},
}
for var_type, value in pairs(damage) do
-- covers the min/max/avg range
for short_key, range_data in pairs(h.range_map) do
value[short_key] = 0
for _, damage_key in ipairs(data.damage_args) do
value[short_key] = value[short_key] + (tpl_args[string.format('%s_%s%s', damage_key, var_type, range_data.var)] or 0)
end
end
end
local value = {}
for short_key, range_data in pairs(h.range_map) do
local result = (damage.min[short_key] + damage.max[short_key]) / 2 * tpl_args[string.format('attack_speed%s', range_data.var)]
value[short_key] = result
tpl_args[string.format('%s%s', data.field, range_data.var)] = result
end
if value.avg > 0 then
h.handle_range_args(tpl_args, frame, data.name, data.field, value, data.html_fmt_options or {})
end
end
end
-- late processing
core.process_arguments(tpl_args, frame, {array=core.late_args})
core.process_arguments(tpl_args, frame, {array=core.item_classes[tpl_args.class].late_args})
-- Handle upgrade from restrictions/info
core.process_upgraded_from(tpl_args, frame)
-- ------------------------------------------------------------------------
-- Infobox handling
-- ------------------------------------------------------------------------
local extra_class = ''
local container = mw.html.create('span')
:attr( 'class', 'item-box -' .. tpl_args.frame_type)
if tpl_args.class == 'Divination Card' then
container
:tag('span')
:attr( 'class', 'divicard-wrapper')
:tag('span')
:attr('class', 'divicard-art')
:wikitext( '[[' .. tpl_args.card_art .. '|link=|alt=]]' )
:done()
:tag('span')
:attr('class', 'divicard-frame')
:wikitext( '[[File:Divination card frame.png|link=|alt=]]' )
:done()
:tag('span')
:attr('class', 'divicard-header')
:wikitext(tpl_args.name)
:done()
:tag('span')
:attr('class', 'divicard-stack')
:wikitext(tpl_args.stack_size)
:done()
:tag('span')
:attr('class', 'divicard-reward')
:tag('span')
:wikitext(tpl_args.description)
:done()
:done()
:tag('span')
:attr('class', 'divicard-flavour text-color -flavour')
:tag('span')
:wikitext(tpl_args.flavour_text)
:done()
:done()
:done()
--TODO Extras?
else
local header_css
if tpl_args.base_item and tpl_args.rarity ~= 'Normal' then
line_type = 'double'
else
line_type = 'single'
end
local name_line = tpl_args.name
if tpl_args.base_item and tpl_args.base_item ~= 'Prophecy' then
name_line = name_line .. '<br>' .. tpl_args.base_item
end
container
:tag('span')
:attr( 'class', 'header -' .. line_type )
:wikitext( name_line )
:done()
core.display.add_to_container_from_map(tpl_args, frame, container, core.item_display_groups)
end
if tpl_args.skill_icon ~= nil then
container:wikitext(string.format('[[%s]]', tpl_args.skill_icon))
end
-- Store the infobox so it can be accessed with ease on other pages
tpl_args.html = tostring(container)
if tpl_args.inventory_icon ~= nil and tpl_args.class ~= 'Divination Card' then
container:wikitext(string.format('[[%s|%sx%spx]]', tpl_args.inventory_icon, c.image_size_full*tpl_args.size_x, c.image_size_full*tpl_args.size_y))
end
--
-- Secondary infobox
--
local extra_infobox = mw.html.create('span')
:attr( 'class', 'item-box -' .. tpl_args.frame_type)
core.display.add_to_container_from_map(tpl_args, frame, extra_infobox, core.extra_display_groups)
--
-- Output
--
local infobox = mw.html.create('span')
infobox
:attr('class', 'infobox-page-container')
:node(container)
:node(extra_infobox)
if tpl_args.skill_screenshot then
infobox:wikitext(string.format('<br>[[%s|300px]]', tpl_args.skill_screenshot))
end
local out = tostring(infobox)
-- ------------------------------------------------------------------------
-- Category handling
-- ------------------------------------------------------------------------
local cats = {}
if tpl_args.rarity == 'Unique' then
cats[#cats+1] = string.format(i18n.categories.unique_affix, tpl_args.class)
elseif tpl_args.base_item == 'Prophecy' then
cats[#cats+1] = i18n.categories.prophecies
elseif tpl_args.is_talisman then
cats[#cats+1] = i18n.categories.talismans
elseif tpl_args.is_essence then
cats[#cats+1] = i18n.categories.essences
else
cats[#cats+1] = tpl_args.class
end
for _, attr in ipairs(m_game.constants.attributes) do
if tpl_args[attr.long_lower .. '_percent'] then
cats[#cats+1] = string.format('%s %s', attr.long_upper, tpl_args.class)
end
end
local affix
if tpl_args.class == 'Active Skill Gems' or tpl_args.class == 'Support Skill Gems' then
affix = i18n.categories.gem_tag_affix
end
if affix ~= nil then
for _, tag in ipairs(tpl_args.gem_tags) do
cats[#cats+1] = string.format(affix, tag)
end
end
if #tpl_args.alternate_art_inventory_icons > 0 then
cats[#cats+1] = i18n.categories.alternate_artwork
end
-- TODO: add maintenance categories
if tpl_args.release_version == nil then
cats[#cats+1] = i18n.categories.missing_release_version
end
if tpl_args._flags.text_modifier then
cats[#cats+1] = i18n.categories.improper_modifiers
end
if tpl_args._flags.broken_upgraded_from_reference then
cats[#cats+1] = i18n.categories.broken_upgraded_from_reference
end
out = out .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
--
-- Misc
--
-- Also show the infobox for areas right away for maps, since they're both on the same page
local query_id
if tpl_args.rarity == 'Normal' and tpl_args.map_area_id ~= nil then
query_id = tpl_args.map_area_id
elseif tpl_args.rarity == 'Unique' and unique_map_area_id ~= nil then
local query_id = tpl_args.unique_map_area_id
end
if query_id then
out = out .. m_area.query_area_info{cats=yes, where=string.format('areas.id="%s"', query_id)}
end
-- ------------------------------------------------------------------------
-- Store cargo data
-- ------------------------------------------------------------------------
-- Map argument values for cargo storage
for _, table_name in ipairs(core.item_classes[tpl_args.class].tables) do
tpl_args._subobjects[table_name] = {
_table = table_name,
}
end
for k, v in pairs(tpl_args) do
local data = core.map[k]
if data ~= nil then
if data.table ~= nil and data.field ~= nil then
tpl_args._subobjects[data.table][data.field] = v
elseif data.table == nil and data.field ~= nil then
error(string.format('Missing table for field "%s", key "%s", value "%s", data:\n%s', data.field, k, v, mw.dumpObject(data)))
elseif data.table ~= nil and data.field == nil then
error(string.format('Missing field for table "%s", key "%s", value "%s", data:\n%s', data.table, k, v, mw.dumpObject(data)))
end
end
end
for _, data in pairs(tpl_args._subobjects) do
m_util.cargo.store(frame, data)
end
mw.logObject(os.clock() - t)
-- Show additional error messages in console to help fixing them
mw.logObject(table.concat(tpl_args._errors, '\n'))
return out
end
-- ----------------------------------------------------------------------------
-- Result formatting templates for SMW queries
-- ----------------------------------------------------------------------------
--
-- Template:
--
function p.simple_item_list(frame)
-- Args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
local query = {}
for key, value in pairs(tpl_args) do
if string.sub(key, 0, 2) == 'q_' then
query[string.sub(key, 3)] = value
end
end
local fields = {
'items._pageName',
'items.name',
}
if tpl_args.no_icon == nil then
fields[#fields+1] = 'items.inventory_icon'
end
if tpl_args.no_html == nil then
fields[#fields+1] = 'items.html'
end
local results = m_util.cargo.query(
{'items'},
fields,
query
)
local out = {}
for _, row in ipairs(results) do
local link = f_item_link{page=tpl_args['items._pageName'], name=tpl_args['items.name'], inventory_icon=tpl_args['items.inventory_icon'] or '', html=tpl_args['items.html'] or '', skip_query=true}
if args.format == nil then
out[#out+1] = string.format('* %s', link)
elseif args.format == 'none' then
out[#out+1] = link
elseif args.format == 'li' then
out[#out+1] = string.format('<li>%s</li>', link)
else
error(string.format(i18n.errors.generic_argument_parameter, 'format', args.format))
end
end
if args.format == nil then
return table.concat(out, '\n')
elseif args.format == 'none' then
return table.concat(out, '\n')
elseif args.format == 'li' then
return table.concat(out)
end
end
-- ----------------------------------------------------------------------------
-- Reponsibile for subtemplates of Template:SMW item table
--
function p.item_table(frame)
-- args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
local modes = {
skill = {
data = core.result.skill_gem_new,
header = i18n.item_table.skill_gem,
},
item = {
data = core.result.generic_item,
header = i18n.item_table.item,
},
}
if tpl_args.mode == nil then
tpl_args.mode = 'item'
end
if modes[tpl_args.mode] == nil then
error(i18n.errors.invalid_item_table_mode)
end
local row_infos = {}
for _, row_info in ipairs(modes[tpl_args.mode].data) do
local enabled = false
if row_info.arg == nil then
enabled = true
elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then
enabled = true
elseif type(row_info.arg) == 'table' then
for _, argument in ipairs(row_info.arg) do
if m_util.cast.boolean(tpl_args[argument]) then
enabled = true
break
end
end
end
if enabled then
row_info.options = row_info.options or {}
row_infos[#row_infos+1] = row_info
end
end
-- Parse stat arguments
local stat_columns = {}
local query_stats = {}
local stat_results = {}
local i = 0
repeat
i = i + 1
local prefix = string.format('stat_column%s_', i)
local col_info = {
header = tpl_args[prefix .. 'header'] or tostring(i),
format = tpl_args[prefix .. 'format'],
stat_format = tpl_args[prefix .. 'stat_format'] or 'separate',
order = tonumber(tpl_args[prefix .. 'order']) or (10000000 + i),
stats = {},
options = {},
}
local j = 0
repeat
j = j +1
local stat_info = {
id = tpl_args[string.format('%sstat%s_id', prefix, j)],
}
if stat_info.id then
col_info.stats[#col_info.stats+1] = stat_info
query_stats[stat_info.id] = {}
else
-- Stop iteration entirely if this was the first index but no stat was supplied. We assume that we stop in this case.
if j == 1 then
i = nil
end
-- stop iteration
j = nil
end
until j == nil
-- Don't add this column if no stats were provided.
if #col_info.stats > 0 then
stat_columns[#stat_columns+1] = col_info
end
until i == nil
for _, col_info in ipairs(stat_columns) do
local row_info = {
--arg
header = col_info.header,
fields = {},
display = function(tr, data, properties)
if col_info.stat_format == 'separate' then
local stat_texts = {}
local num_stats = 0
local vmax = 0
for _, stat_info in ipairs(col_info.stats) do
num_stats = num_stats + 1
-- stat results from outside body
local stat = (stat_results[data['items._pageName']] or {})[stat_info.id]
if stat ~= nil then
stat_texts[#stat_texts+1] = h.format_value(tpl_args, frame, stat, {no_color=true})
vmax = vmax + stat.max
end
end
if num_stats ~= #stat_texts then
tr:wikitext(m_util.html.td.na())
else
local text
if col_info.format then
text = string.format(col_info.format, unpack(stat_texts))
else
text = table.concat(stat_texts, ', ')
end
tr:tag('td')
:attr('data-sort-value', vmax)
:attr('class', 'tc -mod')
:wikitext(text)
end
elseif col_info.stat_format == 'add' then
local total_stat = {
min = 0,
max = 0,
avg = 0,
}
for _, stat_info in ipairs(col_info.stats) do
local stat = (stat_results[data['items._pageName']] or {})[stat_info.id]
if stat ~= nil then
for k, v in pairs(total_stat) do
total_stat[k] = v + stat[k]
end
end
end
if col_info.format == nil then
col_info.format = '%s'
end
tr:tag('td')
:attr('data-sort-value', total_stat.max)
:attr('class', 'tc -mod')
:wikitext(string.format(col_info.format, h.format_value(tpl_args, frame, total_stat, {no_color=true})))
else
error(string.format(i18n.errors.generic_argument_parameter, 'stat_format', col_info.stat_format))
end
end,
order = col_info.order,
}
table.insert(row_infos, row_info)
end
-- sort the rows
table.sort(row_infos, function (a, b)
return (a.order or 0) < (b.order or 0)
end)
-- Parse query arguments
local tables_assoc = {items=true}
local fields = {
'items._pageName',
'items.name',
'items.inventory_icon',
'items.html',
'items.size_x',
'items.size_y',
}
--
local prepend = {
q_join=true,
q_groupBy=true,
q_tables=true
}
local query = {}
for key, value in pairs(tpl_args) do
if string.sub(key, 0, 2) == 'q_' then
if prepend[key] then
value = ',' .. value
end
query[string.sub(key, 3)] = value
end
end
--query.limit = 5000
for _, rowinfo in ipairs(row_infos) do
if type(rowinfo.fields) == 'function' then
rowinfo.fields = rowinfo.fields()
end
for index, field in ipairs(rowinfo.fields) do
rowinfo.options[index] = rowinfo.options[index] or {}
fields[#fields+1] = field
tables_assoc[m_util.string.split(field, '%.')[1]] = true
end
end
-- reformat the fields & tables so they can be retrieved correctly
for index, field in ipairs(fields) do
fields[index] = string.format('%s=%s', field, field)
end
local tables = {}
for table_name,_ in pairs(tables_assoc) do
tables[#tables+1] = table_name
end
-- take care of required joins according to the tables
local joins = {}
for index, table_name in ipairs(tables) do
if table_name ~= 'items' then
joins[#joins+1] = string.format('items._pageID=%s._pageID', table_name)
end
end
query.join = table.concat(joins, ',') .. (query.join or '')
-- Cargo workaround: avoid duplicates using groupBy
query.groupBy = 'items._pageID' .. (query.groupBy or '')
local results = cargo.query(
table.concat(tables,',') .. (tpl_args.q_tables or ''),
table.concat(fields,','),
query
)
if #results == 0 and tpl_args.default ~= nil then
return tpl_args.default
end
if #stat_columns > 0 then
local continue = true
local offset = 0
while continue do
if tpl_args.q_where then
tpl_args.q_where = string.format(' AND (%s)', tpl_args.q_where)
else
tpl_args.q_where = ''
end
local temp = cargo.query(
'items,item_stats' .. (tpl_args.q_tables or ''),
'item_stats._pageName=item_stats._pageName, item_stats.id=item_stats.id, item_stats.min=item_stats.min, item_stats.avg=item_stats.avg, item_stats.max=item_stats.max',
{
where='item_stats.is_implicit IS NULL' .. tpl_args.q_where,
join='items._pageID=item_stats._pageID' .. (tpl_args.q_join or ''),
limit=5000,
--offset = offset,
}
)
for _, row in ipairs(temp) do
if query_stats[row['item_stats.id']] ~= nil then
local stat = {
min = tonumber(row['item_stats.min']),
max = tonumber(row['item_stats.max']),
avg = tonumber(row['item_stats.avg']),
}
if stat_results[row['item_stats._pageName']] == nil then
stat_results[row['item_stats._pageName']] = {[row['item_stats.id']] = stat}
else
stat_results[row['item_stats._pageName']][row['item_stats.id']] = stat
end
end
end
-- Cargo doesnt support offset yet
if #temp == 5000 then
--TODO: Cargo
error('Stats > 5000')
--offset = offset + 5000
else
continue = false
end
end
end
local tbl = mw.html.create('table')
tbl:attr('class', 'wikitable sortable item-table')
-- Header
local tr = tbl:tag('tr')
tr
:tag('th')
:wikitext(modes[tpl_args.mode].header)
:done()
for _, row_info in ipairs(row_infos) do
tr
:tag('th')
:attr('data-sort-type', row_info.sort_type or 'number')
:wikitext(row_info.header)
:done()
end
for _, row in ipairs(results) do
tr = tbl:tag('tr')
local il_args = {
skip_query=true,
page=row['items._pageName'],
name=row['items.name'],
inventory_icon=row['items.inventory_icon'],
html=row['items.html'],
width=row['items.size_x'],
height=row['items.size_y'],
}
if tpl_args.large then
il_args.large = tpl_args.large
end
tr
:tag('td')
:wikitext(f_item_link(il_args))
:done()
for _, rowinfo in ipairs(row_infos) do
-- this has been cast from a function in an earlier step
local display = true
for index, field in ipairs(rowinfo.fields) do
-- this will bet set to an empty value not nil confusingly
if row[field] == '' then
if rowinfo.options[index].optional ~= true then
display = false
break
else
row[field] = nil
end
end
end
if display then
rowinfo.display(tr, row, rowinfo.fields)
else
tr:wikitext(m_util.html.td.na())
end
end
end
return tostring(tbl)
end
item_table_factory = {}
function item_table_factory.intro(args)
-- args:
-- data_array
-- header
return function (frame)
-- Args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
--
tpl_args.userparam = m_util.string.split_args(tpl_args.userparam, {sep=', '})
local tr = mw.html.create('tr')
tr
:tag('th')
:wikitext(args.header)
:done()
for _, rowinfo in ipairs(args.data_array) do
if rowinfo.arg == nil or tpl_args.userparam[rowinfo.arg] then
tr
:tag('th')
:wikitext(rowinfo.header)
:done()
end
end
return '<table class="wikitable sortable item-table">' .. tostring(tr)
end
end
-- ----------------------------------------------------------------------------
-- Item lists
-- ----------------------------------------------------------------------------
function p.skill_gem_list_by_gem_tag(frame)
-- Args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
if tpl_args.class == 'Support Skill Gems' then
elseif tpl_args.class == 'Active Skill Gems' then
else
error(i18n.errors.invalid_item_class)
end
local query = {}
query[#query+1] = string.format('[[Has item class::%s]]', tpl_args.class)
query[#query+1] = '?Has gem tags'
query[#query+1] = '?Has name'
query[#query+1] = '?Has inventory icon'
--query[#query+1] = '?Has infobox HTML'
query.limit = 5000
query.sort = 'Has name'
local results = m_util.smw.query(query, frame)
local tags = {}
for _, row in ipairs(results) do
row['Has gem tags'] = m_util.string.split(row['Has gem tags'], '<MANY>')
for _, tag in ipairs(row['Has gem tags']) do
if tags[tag] == nil then
tags[tag] = {}
end
table.insert(tags[tag], row)
end
end
local tags_sorted = {}
for tag, _ in pairs(tags) do
table.insert(tags_sorted, tag)
end
table.sort(tags_sorted)
local tbl = mw.html.create('table')
tbl
:attr('class', 'wikitable sortable')
:tag('tr')
:tag('th')
:wikitext('Tag')
:done()
:tag('th')
:wikitext('Skills')
:done()
:done()
for _, tag in ipairs(tags_sorted) do
local rows = tags[tag]
local tr = tbl:tag('tr')
tr
:tag('td')
:wikitext(tag)
local td = tr:tag('td')
for i, row in ipairs(rows) do
td:wikitext(f_item_link{page=row[1], name=row['Has Name'], inventory_icon=row['Has inventory icon'], html=row['Has infobox HTML'] or ''})
if i < #rows then
td:wikitext('<br>')
end
end
end
return tostring(tbl)
end
-- ----------------------------------------------------------------------------
-- Misc. Item templates
-- ----------------------------------------------------------------------------
--
-- Template: Item acquisition
--
-- Used to duplicate the information from the infobox in a more readable manner on the page.
function p.item_acquisition (frame)
-- Get args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
tpl_args.page = tpl_args.page or tostring(mw.title.getCurrentTitle())
local out = {}
local results
local query
-- -------------------------
-- Drop restrictions by area
-- -------------------------
results = m_util.cargo.query(
{'items'},
{'items.drop_areas_html'},
{
where=string.format('items._pageName="%s"', tpl_args.page),
-- Workaround: Fix cargo duplicates
groupBy='items._pageID',
}
)
if #results > 0 then
results = results[1]
if results['items.drop_areas_html'] then
local ul = mw.html.create('ul')
for _, area in ipairs(m_util.string.split(results['items.drop_areas_html'], ',%s*')) do
ul:tag('li')
:wikitext(area)
end
out[#out+1] = i18n.acquisition.area
out[#out+1]= '<br>'
out[#out+1] = tostring(ul)
end
end
-- ------------------------------------
-- Obtained via vendor recipes/upgrades
-- ------------------------------------
--
-- Query data
--
local sets = {}
results = m_util.cargo.query(
{'upgraded_from_sets'},
{
'upgraded_from_sets.set_id',
'upgraded_from_sets.text',
},
{
where=string.format('upgraded_from_sets._pageName="%s"', tpl_args.page),
-- Workaround: Fix cargo duplicates
groupBy='upgraded_from_sets._pageID,upgraded_from_sets.set_id',
}
)
for _, row in ipairs(results) do
row.groups = {}
sets[tonumber(row['upgraded_from_sets.set_id'])] = row
end
results = m_util.cargo.query(
{'upgraded_from_groups'},
{
'upgraded_from_groups.set_id',
'upgraded_from_groups.group_id',
'upgraded_from_groups.notes',
'upgraded_from_groups.amount',
'upgraded_from_groups.item_name',
'upgraded_from_groups.item_page',
},
{
where=string.format('upgraded_from_groups._pageName="%s"', tpl_args.page),
-- Workaround: Fix cargo duplicates
groupBy='upgraded_from_groups._pageID,upgraded_from_groups.set_id,upgraded_from_groups.group_id',
}
)
for _, row in ipairs(results) do
sets[tonumber(row['upgraded_from_groups.set_id'])].groups[tonumber(row['upgraded_from_groups.group_id'])] = row
end
--
-- Build output
--
if #sets > 0 then
local ul = mw.html.create('ul')
for _, set in ipairs(sets) do
local li = ul:tag('li')
if set['upgraded_from_sets.text'] then
li:wikitext(set['upgraded_from_sets.text'] .. '<br>')
end
local str = {}
for _, group in ipairs(set.groups) do
str[#str+1] = string.format('%sx [[%s|%s]]', group['upgraded_from_groups.amount'], group['upgraded_from_groups.item_page'], group['upgraded_from_groups.item_name'] or group['upgraded_from_groups.item_page'])
end
li:wikitext(table.concat(str, ', '))
end
out[#out+1] = i18n.acquisition.upgraded_from
out[#out+1]= '<br>'
out[#out+1] = tostring(ul)
end
out[#out+1] = tpl_args.acquisition_insert
-- -------------------------------------
-- Ingredient of vendor recipes/upgrades
-- -------------------------------------
--
-- Query
--
-- TODO: IL links
results = m_util.cargo.query(
{'upgraded_from_groups'},
{'upgraded_from_groups._pageName'},
{
where=string.format('upgraded_from_groups.item_page="%s"', tpl_args.page),
-- Only need each page name once
groupBy='upgraded_from_groups._pageName',
}
)
if #results > 0 then
local head = mw.html.create('h3')
head:wikitext(i18n.acquisition.ingredient_header)
out[#out+1] = tostring(head)
out[#out+1] = i18n.acquisition.ingredient
out[#out+1]= '<br>'
local ul = mw.html.create('ul')
for _, row in ipairs(results) do
ul:tag('li')
:wikitext(string.format('[[%s]]', row['upgraded_from_groups._pageName']))
end
out[#out+1] = tostring(ul)
end
out[#out+1] = tpl_args.ingredient_append
-- ------------------------------------
-- output
-- ------------------------------------
local head = mw.html.create('h2')
head:wikitext(i18n.acquisition.header .. '[[File:Questionmark.png|right|24px|link=Path_of_Exile_Wiki:How_to_edit_item_acquisition]]')
return tostring(head) .. table.concat(out)
end
--
-- Template:Item class
--
function p.item_class (frame)
-- Get args
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
if not doInfoCard then
doInfoCard = require('Module:Infocard')._main
end
m_util.cast.factory.table('name', {key='full', tbl=m_game.constants.item.class})(tpl_args, frame)
if tpl_args.name_list ~= nil then
tpl_args.name_list = m_util.string.split(tpl_args.name_list, ',%s*')
else
tpl_args.name_list = {}
end
--
local ul = mw.html.create('ul')
for _, item in ipairs(tpl_args.name_list) do
ul
:tag('li')
:wikitext(item)
:done()
end
-- Output Infocard
local tplargs = {
['header'] = tpl_args.name,
['subheader'] = i18n.item_class_infobox.page .. i18n.item_class_infobox.info,
[1] = i18n.item_class_infobox.also_referred_to_as .. tostring(ul),
}
-- cats
local cats = {
'Item classes',
tpl_args.name,
}
-- Done
return doInfoCard(tplargs) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug})
end
-- ----------------------------------------------------------------------------
-- Debug stuff
-- ----------------------------------------------------------------------------
p.debug = {}
function p.debug._tbl_data(tbl)
keys = {}
for _, data in ipairs(core.result.generic_item) do
if type(data.arg) == 'string' then
keys[data.arg] = 1
elseif type(data.arg) == 'table' then
for _, arg in ipairs(data.arg) do
keys[arg] = 1
end
end
end
local out = {}
for key, _ in pairs(keys) do
out[#out+1] = string.format("['%s'] = '1'", key)
end
return table.concat(out, ', ')
end
function p.debug.generic_item_all()
return p.debug._tbl_data(core.result.generic_item)
end
function p.debug.skill_gem_all()
return p.debug._tbl_data(core.result.skill_gem_new)
end
return p