[dismiss]
The wiki is currently a work in progress. If you'd like to help out, please check the Community Portal and our getting started guide. Also, check out our sister project on poewiki.net.
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