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