Module:Item table

-- Item table -- -- Creates various item tables from cargo queries. -- -- -- Todo list -- - -- * Handle table columns that can have multiple cargo rows, preferably --  in one or two queries. Not per column AND row. -- * Handle template include size? Remove item link when getting close --  to the limit? -- * Add a wrapper around f_item_link to be able to disable all --  hoverboxes, to avoid running into template expansion size issues.

-- -- Imports -- local m_util = require('Module:Util') local getArgs = require('Module:Arguments').getArgs local m_game = require('Module:Game') local f_item_link = require('Module:Item link').item_link local f_skill_link = require('Module:Skill link').skill_link local m_cargo = require('Module:Cargo')

-- -- Globals --

local c = {} c.query_default = 50 c.query_max = 300

-- -- 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.

local i18n = { categories = { -- maintenance cats query_limit = 'Item tables hitting query limit', query_hard_limit = 'Item tables hitting hard query limit', no_results = 'Item tables without results', },

-- 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', metadata_id = 'Metadata ID', item_class = 'Item Class', rarity = 'Rarity', rarity_id = 'Rarity ID', essence_level = 'Essence Level', drop_level = 'Drop Level', release_version = 'Release Version', removal_version = 'Removal Version', version_link = '%s', drop_enabled = m_util.html.abbr('Drop Enabled', 'If an item is drop disabled, it can not be normally obtained, but still may be available under specific conditions (like trading via standard league or limited time events'),       drop_leagues = 'Drop Leagues',        drop_leagues_link = '%s',        drop_areas = 'Drop Areas',        drop_monsters = 'Drop Monsters',        drop_text = 'Additional Drop Restrictions',        stack_size = 'Stack Size',        stack_size_currency_tab = m_util.html.abbr('Tab Stack 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_life_per_second = m_util.html.abbr('Life/s', 'Life regenerated each second'), flask_life_per_charge = m_util.html.abbr('Life/c', 'Life regenerated per flask charge'), flask_mana = m_util.html.abbr('Mana', 'Mana regenerated over the flask duration'), flask_mana_per_second = m_util.html.abbr('Mana/s', 'Mana regenerated each second'), flask_mana_per_charge = m_util.html.abbr('Mana/c', 'Mana regenerated per flask charge'), 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'), seed_type = 'Seed Type', seed_tier = 'Seed Tier', seed_growth_cycles = 'Growth Cycles', seed_consumed_primal_lifeforce_percentage = m_util.html.abbr('Primal Lifeforce consumed', 'How much of the consumed life force is of the primal type.'), seed_consumed_vivid_lifeforce_percentage = m_util.html.abbr('Vivid Lifeforce consumed', 'How much of the consumed life force is of the vivid type.'), seed_consumed_wild_lifeforce_percentage = m_util.html.abbr('Wild Lifeforce consumed', 'How much of the consumed life force is of the wild type.'), seed_required_nearby_seed_amount = m_util.html.abbr('Nearby Seeds required', 'How many seeds of the same type are required for this seed.'), seed_required_nearby_seed_tier = m_util.html.abbr('Nearby Seed Tier', 'The minimum tier required of nearby seeds of the same type.'), seed_granted_crafting_options = 'Crafting Options', item_limit = 'Limit', jewel_radius = 'Radius', map_tier = 'Map Tier', map_level = 'Map Level', map_guild_character = m_util.html.abbr('Char', 'Character for the guild tag'), atlas_tier = 'Atlas map tier based on region', atlas_level = 'Atlas map level based on region', master_level_requirement = '', master = 'Master', master_favour_cost = 'Favour Cost', variation_count = 'Variations', buff_effects = 'Buff Effects', stats = 'Stats', quality_stats = 'Stats per 1% Quality', effects = 'Effect(s)', incubator_effect = 'Incubation Effect', flavour_text = 'Flavour Text', prediction_text = 'Prediction', help_text = 'Help Text', seal_cost = m_util.html.abbr('Seal Cost', 'Silver Coin cost of sealing this prophecies into an item'), objective = 'Objective', reward = 'Reward', buff_icon = 'Buff Icon', quest_name = 'Quest', quest_act = 'Quest Act', purchase_costs = m_util.html.abbr('Purchase Cost', '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.'), boss_name = 'Boss', boss_number = 'Number of bosses', legacy = m_util.html.abbr('Legacy stats', 'Compare legacy variants to the current one.&#10;• Bright text indicates modifiers that are different from the latest variant.&#10;• Strike-through text indicates modifiers that do not exist on legacy variants.'), granted_skills = 'Granted skills', granted_skills_level_label = 'Level', granted_skills_level_pattern = '{granted_skills_level_label}%s*(%d+)', granted_skills_level_format = '{granted_skills_level_label} {level_number} ', granted_skills_skill_output_format = '{level}{sl}', granted_skills_gem_output_format = '{level}{il}', alternate_art = 'Alternate Arts',

-- 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 Time', 'Casting time of the skill in seconds'), attack_speed_multiplier = m_util.html.abbr('ASPD', 'Attack Speed Multiplier'), damage_effectiveness = m_util.html.abbr('Dmg. Eff.', 'Effectiveness of Added Damage'), 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 (1.5x in part 2, 2x in maps)'), 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'), vendor_rewards = m_util.html.abbr('Vendor rewards', 'Vendor rewards after quest completion'), vendor_rewards_row_format = 'Act %s after %s from %s with %s.', vendor_rewards_any_classes = 'any character', quest_rewards = m_util.html.abbr('Quest rewards', 'Rewards after quest completion'), quest_rewards_row_format = 'Act %s after %s with %s.', quest_rewards_any_classes = 'any character', },

prophecy_description = { objective = 'Objective', reward = 'Reward', },

item_disambiguation = { original='the original variant', drop_enabled='the current drop enabled variant', drop_disabled='a legacy variant', known_release = ' that was introduced in %s', list_pattern='%s, %s%s.' },

errors = { generic_argument_parameter = 'Unrecognized %s parameter "%s"', invalid_item_table_mode = 'Invalid mode for item table', }, }

-- -- Helper & utility functions --

local h = {}

function h.na_or_val(tr, value, func) if value == nil or value == '' 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

h.tbl = {}

function h.tbl.range_fields(args) local suffixes = {'maximum', 'text', 'colour'} if args.full then suffixes[#suffixes+1] = 'minimum' end return function local fields = {} function inner(field) for _, partial_field in ipairs(suffixes) do               fields[#fields+1] = string.format('%s_range_%s', field, partial_field) end end if type(args.field) == 'table' then for _, field in ipairs(args.field) do               inner(field) end else inner(args.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, fields, data2) local values = {} local fmt_values = {} local sdata = data2.skill_levels[data['items._pageName']]

for index, field in ipairs(fields) do           local value = { min=data[field], max=data[field], base=data[field], }           if sdata then value.min = value.min or sdata['0'][field] or sdata['1'][field] value.max = value.max or sdata['0'][field] or sdata[data['skill.max_level']][field] end if value.min then values[#values+1] = value.max local opts = args.options[index] or {} -- global colour is set, no overrides if args.colour ~= nil then opts.no_color = true end fmt_values[#fmt_values+1] = m_util.html.format_value(nil, nil, value, opts) end end

if #values == 0 then tr:wikitext(m_util.html.td.na) else local td = tr:tag('td') td:attr('data-sort-value', table.concat(values, ', ')) td:wikitext(table.concat(fmt_values, ', ')) if args.colour then td:attr('class', 'tc -' .. args.colour) end 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.tbl.display.factory.range_composite(args) -- division by default if args.func == nil then args.func = function (a, b)           if b == 0 then return 'fail' end return a / b       end end return function(tr, data, fields) local field = {} for i=1, 2 do            local fieldn = args['field' .. i]           field[i] = { min = tonumber(data[string.format('%s_range_minimum', fieldn)]) or 0, max = tonumber(data[string.format('%s_range_maximum', fieldn)]) or 0, color = data[string.format('%s_range_colour', fieldn)] or 'default', }       end field.min = args.func(field[1].min, field[2].min) field.max = args.func(field[1].max, field[2].max) if field.min == 'fail' or field.max == 'fail' then field.text = '' field.color = 'default' else for i=1, 2 do               if field[i].color ~= 'default' then field.color = field[i].color break end end if field.min == field.max then field.text = string.format('%.2f', field.min) else field.text = string.format('(%.2f-%.2f)', field.min, field.max) end end tr           :tag('td') :attr('data-sort-value', field.max) :attr('class', 'tc -' .. field.color) :wikitext(field.text) :done end end

function h.tbl.display.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 h.tbl.display.factory.atlas_tier(args) args = args or {} return function (tr, data) for i=0,4 do           local t = tonumber(data['atlas_maps.map_tier' .. i])

if t == 0 then tr                   :tag('td') :attr('table-sort-value', 0) :attr('class', 'table-cell-xmark') :wikitext('✗') else if args.is_level then t = t + 67 end tr                   :tag('td') :attr('class', 'tc -value') :wikitext(t) :done end end end end

-- -- Data mappings --

local data_map = {}

-- for sort type see: -- https://meta.wikimedia.org/wiki/Help:Sorting data_map.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', 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 = 'rarity', header = i18n.item_table.rarity, fields = {'items.rarity'}, display = h.tbl.display.factory.value{}, order = 1002, },   {        arg = 'rarity_id', header = i18n.item_table.rarity_id, fields = {'items.rarity_id'}, display = h.tbl.display.factory.value{}, order = 1003, },   {        arg = 'metadata_id', header = i18n.item_table.metadata_id, fields = {'items.metadata_id'}, display = h.tbl.display.factory.value{}, order = 1004, },   {        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{field='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{field='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{field='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{field='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{field='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{field='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{field='weapons.critical_strike_chance'}, display = h.tbl.display.factory.range{field='weapons.critical_strike_chance'}, order = 8002, },   {        arg = {'physical_dps'}, header = i18n.item_table.physical_dps, fields = h.tbl.range_fields{field='weapons.physical_dps'}, display = h.tbl.display.factory.range{field='weapons.physical_dps'}, order = 8100, },   {        arg = {'lightning_dps'}, header = i18n.item_table.lightning_dps, fields = h.tbl.range_fields{field='weapons.lightning_dps'}, display = h.tbl.display.factory.range{field='weapons.lightning_dps'}, order = 8101, },   {        arg = {'cold_dps'}, header = i18n.item_table.cold_dps, fields = h.tbl.range_fields{field='weapons.cold_dps'}, display = h.tbl.display.factory.range{field='weapons.cold_dps'}, order = 8102, },   {        arg = {'fire_dps'}, header = i18n.item_table.fire_dps, fields = h.tbl.range_fields{field='weapons.fire_dps'}, display = h.tbl.display.factory.range{field='weapons.fire_dps'}, order = 8103, },   {        arg = {'chaos_dps'}, header = i18n.item_table.chaos_dps, fields = h.tbl.range_fields{field='weapons.chaos_dps'}, display = h.tbl.display.factory.range{field='weapons.chaos_dps'}, order = 8104, },   {        arg = {'elemental_dps'}, header = i18n.item_table.elemental_dps, fields = h.tbl.range_fields{field='weapons.elemental_dps'}, display = h.tbl.display.factory.range{field='weapons.elemental_dps'}, order = 8105, },   {        arg = {'poison_dps'}, header = i18n.item_table.poison_dps, fields = h.tbl.range_fields{field='weapons.poison_dps'}, display = h.tbl.display.factory.range{field='weapons.poison_dps'}, order = 8106, },   {        arg = {'dps'}, header = i18n.item_table.dps, fields = h.tbl.range_fields{field='weapons.dps'}, display = h.tbl.display.factory.range{field='weapons.dps'}, order = 8107, },   {        arg = 'flask_life', header = i18n.item_table.flask_life, fields = h.tbl.range_fields{field='flasks.life'}, display = h.tbl.display.factory.range{field='flasks.life'}, order = 9000, },   {        arg = 'flask_life_per_second', header = i18n.item_table.flask_life_per_second, fields = h.tbl.range_fields{field={'flasks.life', 'flasks.duration'}, full=true}, display = h.tbl.display.factory.range_composite{field1='flasks.life', field2='flasks.duration'}, order = 9001, },   {        arg = 'flask_life_per_charge', header = i18n.item_table.flask_life_per_charge, fields = h.tbl.range_fields{field={'flasks.life', 'flasks.charges_per_use'}, full=true}, display = h.tbl.display.factory.range_composite{field1='flasks.life', field2='flasks.charges_per_use'}, order = 9002, },   {        arg = 'flask_mana', header = i18n.item_table.flask_mana, fields = h.tbl.range_fields{field='flasks.mana'}, display = h.tbl.display.factory.range{field='flasks.mana'}, order = 9010, },   {        arg = 'flask_mana_per_second', header = i18n.item_table.flask_mana_per_second, fields = h.tbl.range_fields{field={'flasks.mana', 'flasks.duration'}, full=true}, display = h.tbl.display.factory.range_composite{field1='flasks.mana', field2='flasks.duration'}, order = 9011, },   {        arg = 'flask_mana_per_charge', header = i18n.item_table.flask_mana_per_charge, fields = h.tbl.range_fields{field={'flasks.mana', 'flasks.charges_per_use'}, full=true}, display = h.tbl.display.factory.range_composite{field1='flasks.mana', field2='flasks.charges_per_use'}, order = 9012, },   {        arg = 'flask', header = i18n.item_table.flask_duration, fields = h.tbl.range_fields{field='flasks.duration'}, display = h.tbl.display.factory.range{field='flasks.duration'}, order = 9020, },   {        arg = 'flask', header = i18n.item_table.flask_charges_per_use, fields = h.tbl.range_fields{field='flasks.charges_per_use'}, display = h.tbl.display.factory.range{field='flasks.charges_per_use'}, order = 9030, },   {        arg = 'flask', header = i18n.item_table.flask_maximum_charges, fields = h.tbl.range_fields{field='flasks.charges_max'}, display = h.tbl.display.factory.range{field='flasks.charges_max'}, order = 9031, },   -- Seed data 91xx, {       arg = {'seed', 'seed_type'}, header = i18n.item_table.seed_type, fields = {'harvest_seeds.type', 'harvest_seeds.type_id'}, display = function (tr, data) tr               :tag('td') :attr('table-sort-value', data['harvest_seeds.type']) :attr('class', 'tc -' .. data['harvest_seeds.type_id']) :wikitext(data['harvest_seeds.type']) end, order = 9100, },   {        arg = {'seed', 'seed_tier'}, header = i18n.item_table.seed_tier, fields = {'harvest_seeds.tier'}, display = h.tbl.display.factory.value{}, order = 9101, },   {        arg = {'seed', 'seed_growth_cycles'}, header = i18n.item_table.seed_growth_cycles, fields = {'harvest_seeds.growth_cycles'}, display = h.tbl.display.factory.value{}, order = 9102, },   {        arg = {'seed', 'seed_cosumed_lifeforce', 'seed_consumed_lifeforce_percentage', 'seed_consumed_primal_lifeforce_percentage'}, header = i18n.item_table.seed_consumed_primal_lifeforce_percentage, fields = {'harvest_seeds.consumed_primal_lifeforce_percentage'}, display = h.tbl.display.factory.value{color='primal'}, order = 9110, },   {        arg = {'seed', 'seed_cosumed_lifeforce', 'seed_consumed_lifeforce_percentage', 'seed_consumed_vivid_lifeforce_percentage'}, header = i18n.item_table.seed_consumed_vivid_lifeforce_percentage, fields = {'harvest_seeds.consumed_vivid_lifeforce_percentage'}, display = h.tbl.display.factory.value{color='vivid'}, order = 9110, },   {        arg = {'seed', 'seed_cosumed_lifeforce', 'seed_consumed_lifeforce_percentage', 'seed_consumed_wild_lifeforce_percentage'}, header = i18n.item_table.seed_consumed_wild_lifeforce_percentage, fields = {'harvest_seeds.consumed_wild_lifeforce_percentage'}, display = h.tbl.display.factory.value{color='wild'}, order = 9110, },   {        arg = {'seed', 'seed_required_nearby_seeds', 'seed_required_nearby_seed_amount'}, header = i18n.item_table.seed_required_nearby_seed_amount, fields = {'harvest_seeds.required_nearby_seed_amount'}, display = h.tbl.display.factory.value{}, order = 9113, },   {        arg = {'seed', 'seed_required_nearby_seeds', 'seed_required_nearby_seed_tier'}, header = i18n.item_table.seed_required_nearby_seed_tier, fields = {'harvest_seeds.required_nearby_seed_tier'}, display = h.tbl.display.factory.value{}, order = 9114, },   -- 9120 show crafting options? {       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{}, order = 11020, sort_type = 'text', },   {        arg = 'atlas_tier', header = i18n.item_table.atlas_tier, colspan = 5, fields = {'atlas_maps.map_tier0', 'atlas_maps.map_tier1', 'atlas_maps.map_tier2', 'atlas_maps.map_tier3', 'atlas_maps.map_tier4'}, display = h.tbl.display.factory.atlas_tier{is_level=false}, order = 11050, },   {        arg = 'atlas_level', header = i18n.item_table.atlas_level, colspan = 5, fields = {'atlas_maps.map_tier0', 'atlas_maps.map_tier1', 'atlas_maps.map_tier2', 'atlas_maps.map_tier3', 'atlas_maps.map_tier4'}, display = h.tbl.display.factory.atlas_tier{is_level=true}, order = 11060, },   {        arg = {'doodad', 'master_level_requirement'}, header = i18n.item_table.master_level_requirement, fields = {'hideout_doodads.level_requirement'}, display = h.tbl.display.factory.value{}, order = 11100, },   {        arg = {'doodad', 'master'}, header = i18n.item_table.master, fields = {'hideout_doodads.master'}, display = h.tbl.display.factory.value{}, order = 11101, sort_type = 'text', },   {        arg = {'doodad', 'master_favour_cost'}, header = i18n.item_table.master_favour_cost, fields = {'hideout_doodads.favour_cost'}, display = h.tbl.display.factory.value{colour='currency'}, order = 11102, },   {        arg = {'doodad', 'variation_count'}, header = i18n.item_table.variation_count, fields = {'hideout_doodads.variation_count'}, display = h.tbl.display.factory.value{colour='mod'}, order = 11105, },   {        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 = {'seed', 'seed_effect'}, header = i18n.item_table.effects, fields = {'harvest_seeds.effect'}, display = h.tbl.display.factory.value{colour='crafted'}, order = 12003, sort_type = 'text', },   {        arg = 'incubator_effect', header = i18n.item_table.incubator_effect, fields = {'incubators.effect'}, display = h.tbl.display.factory.value{colour='crafted'}, order = 12004, 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 = 12006, 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 = 12008, 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 = {'version', 'release_version'}, header = i18n.item_table.release_version, fields = {'items.release_version'}, display = function(tr, data) tr               :tag('td') :wikitext(                       string.format( i18n.item_table.version_link, data['items.release_version'], data['items.release_version'] )                   )        end, order = 15000, },   {        arg = {'version', 'removal_version'}, header = i18n.item_table.removal_version, fields = {'items.removal_version'}, display = function(tr, data) tr               :tag('td') :wikitext(                       string.format( i18n.item_table.version_link, data['items.removal_version'], data['items.removal_version'] )                   )        end, order = 15001, },   {        arg = {'drop', 'drop_enabled'}, header = i18n.item_table.drop_enabled, fields = {'items.drop_enabled'}, display = h.tbl.display.factory.value{}, order = 15002, },   {        arg = {'drop', 'drop_leagues'}, header = i18n.item_table.drop_leagues, fields = {'items.drop_leagues'}, display = function(tr, data) local s = m_util.string.split(data['items.drop_leagues'], ',') for i, v in ipairs(s) do               s[i] = string.format(i18n.item_table.drop_leagues_link, v, v)            end tr               :tag('td') :wikitext(table.concat(s, ' ')) end, order = 15003, sort_type = 'text', },   {        arg = {'drop', 'drop_areas'}, header = i18n.item_table.drop_areas, fields = {'items.drop_areas_html'}, display = h.tbl.display.factory.value{}, order = 15004, sort_type = 'text', },   {        arg = {'drop', 'drop_monsters'}, header = i18n.item_table.drop_monsters, fields = {'items.drop_monsters'}, display = function(tr, data, na, results2) if results2['drop_monsters_query'] == nil then results2['drop_monsters_query'] = m_cargo.query(                   {'items', 'monsters', 'main_pages'},                    {                        'items._pageName',                        'monsters._pageName',                        'monsters.name',                        'main_pages._pageName',                    },                    {                        join=                            items.drop_monsters HOLDS monsters.metadata_id,                            monsters.metadata_id=main_pages.id                       ,                        where=string.format(                            items._pageID IN (%s)                            AND monsters.metadata_id IS NOT NULL                        , table.concat(results2.pageIDs, ', ') ),                       orderBy='items.drop_monsters',                    }                )

results2['drop_monsters_query'] = m_cargo.map_results_to_id{ results=results2['drop_monsters_query'], field='items._pageName', }           end local results = results2['drop_monsters_query'][data['items._pageName']] or {} local tbl = {} for _,v in ipairs(results) do               local page = v['main_pages._pageName'] or v['monsters._pageName'] or '' local name = v['monsters.name'] or v['items.drop_monsters'] or '' tbl[#tbl+1] = string.format('%s', page, name) end h.na_or_val(tr, table.concat(tbl, ' ')) end, order = 15005, sort_type = 'text', },   {        arg = {'drop', 'drop_text'}, header = i18n.item_table.drop_text, fields = {'items.drop_text'}, display = h.tbl.display.factory.value{}, order = 15006, sort_type = 'text', },   {        arg = {'quest'}, header = i18n.item_table.quest_rewards, fields = {'items._pageName'}, display = function(tr, data, na, results2) if results2['quest_query'] == nil then results2['quest_query'] = m_cargo.query(                   {'items', 'quest_rewards'},                    {                        'quest_rewards._pageName',                        'quest_rewards.classes',                        'quest_rewards.act',                        'quest_rewards.quest'                    },                    {                        join='items._pageName=quest_rewards._pageName',                        where=string.format( 'items._pageID IN (%s) AND quest_rewards._pageName IS NOT NULL', table.concat(results2.pageIDs, ', ') ),                       orderBy='quest_rewards.act, quest_rewards.quest',                    }                ) results2['quest_query'] = m_cargo.map_results_to_id{ results=results2['quest_query'], field='quest_rewards._pageName', }           end

local results = results2['quest_query'][data['items._pageName']] or {} local tbl = {} for _, v in ipairs(results) do               local classes = table.concat(m_util.string.split(v['quest_rewards.classes'] or '', ',%s*'), ', ') if classes == '' or classes == nil then classes = i18n.item_table.quest_rewards_any_classes end

tbl[#tbl+1] = string.format(                   i18n.item_table.quest_rewards_row_format,                    v['quest_rewards.act'],                    v['quest_rewards.quest'],                    classes                ) end

value = table.concat(tbl, ' ') if value == nil or value == '' then tr:wikitext(m_util.html.td.na) else tr                   :tag('td') :attr('style', 'text-align:left') :wikitext(value) end end, order = 16001, sort_type = 'text', },   {        arg = {'vendor'}, header = i18n.item_table.vendor_rewards, fields = {'items._pageName'}, display = function(tr, data, na, results2) if results2['vendor_query'] == nil then results2['vendor_query'] = m_cargo.query(                   {'items', 'vendor_rewards'},                    {                        'vendor_rewards._pageName',                        'vendor_rewards.classes',                        'vendor_rewards.act',                        'vendor_rewards.npc',                        'vendor_rewards.quest',                    },                    {                        join='items._pageName=vendor_rewards._pageName',                        where=string.format( 'items._pageID IN (%s) AND vendor_rewards._pageName IS NOT NULL', table.concat(results2.pageIDs, ', ') ),                       orderBy='vendor_rewards.act, vendor_rewards.quest',                    }                ) results2['vendor_query'] = m_cargo.map_results_to_id{ results=results2['vendor_query'], field='vendor_rewards._pageName', }           end local results = results2['vendor_query'][data['items._pageName']] or {}

local tbl = {} for _, v in ipairs(results) do               local classes = table.concat(m_util.string.split(v['vendor_rewards.classes'] or '', ',%s*'), ', ') if classes == '' or classes == nil then classes = i18n.item_table.vendor_rewards_any_classes end

tbl[#tbl+1] = string.format(                   i18n.item_table.vendor_rewards_row_format,                    v['vendor_rewards.act'],                    v['vendor_rewards.quest'],                    v['vendor_rewards.npc'],                    classes                ) end

value = table.concat(tbl, ' ') if value == nil or value == '' then tr:wikitext(m_util.html.td.na) else tr                   :tag('td') :attr('style', 'text-align:left') :wikitext(value) end end, order = 17001, sort_type = 'text', },   {        arg = {'price', 'purchase_cost'}, header = i18n.item_table.purchase_costs, fields = {'item_purchase_costs.name', 'item_purchase_costs.amount'}, display = function (tr, data) -- Can purchase costs have multiple currencies and rows? -- Just switch to the same method as in sell_price then. local tbl = {} if data['item_purchase_costs.name'] ~= nil then tbl[#tbl+1] = string.format(                       '%sx %s',                        data['item_purchase_costs.amount'],                        f_item_link{data['item_purchase_costs.name']}                    ) end h.na_or_val(tr, table.concat(tbl, ' ')) end, order = 18001, sort_type = 'text', },   {        arg = {'price', 'sell_price'}, header = i18n.item_table.sell_price, fields = {'item_sell_prices.name', 'item_sell_prices.amount'}, display = function(tr, data, na, results2) if results2['sell_price_query'] == nil then results2['sell_price_query'] = m_cargo.query(                   {'items', 'item_sell_prices'},                    {                        'item_sell_prices.name',                        'item_sell_prices.amount',                        'item_sell_prices._pageID'                    },                    {                        join='items._pageID=item_sell_prices._pageID',                        where=string.format( 'items._pageID IN (%s) AND item_sell_prices._pageID IS NOT NULL', table.concat(results2.pageIDs, ', ') ),                       orderBy='item_sell_prices.name',                    }                ) results2['sell_price_query'] = m_cargo.map_results_to_id{ results=results2['sell_price_query'], field='item_sell_prices._pageID', }           end local results = results2['sell_price_query'][data['items._pageID']] or {}

local tbl = {} for _,v in ipairs(results) do               tbl[#tbl+1] = string.format(                    '%sx %s',                    v['item_sell_prices.amount'],                    f_item_link{v['item_sell_prices.name']}                ) end h.na_or_val(tr, table.concat(tbl, ' ')) end, order = 18002, sort_type = 'text', },   {        arg = {'boss', 'boss_name'}, header = i18n.item_table.boss_name, fields = {'maps.area_id'}, display = function(tr, data, na, results2) if results2['boss_query'] == nil then results2['boss_query'] = m_cargo.query(                   {'items', 'maps', 'areas', 'monsters', 'main_pages'},                    {                        'items._pageName',                        'maps.area_id',                        'areas.id',                        'areas.boss_monster_ids',                        'monsters._pageName',                        'monsters.name',                        'main_pages._pageName',                    },                    {                        join=                            items._pageID=maps._pageID,                            maps.area_id=areas.id,                            areas.boss_monster_ids HOLDS monsters.metadata_id,                            monsters.metadata_id=main_pages.id                       ,                        where=string.format(                            items._pageID IN (%s)                            AND maps.area_id IS NOT NULL                            AND areas.boss_monster_ids HOLDS LIKE "%%"                        , table.concat(results2.pageIDs, ', ') ),                       orderBy='areas.boss_monster_ids',                    }                )

results2['boss_query'] = m_cargo.map_results_to_id{ results=results2['boss_query'], field='items._pageName', }           end local results = results2['boss_query'][data['items._pageName']] or {} local tbl = {} for _,v in ipairs(results) do               local page = v['main_pages._pageName'] or v['monsters._pageName'] or '' local name = v['monsters.name'] or v['areas.boss_monster_ids'] or '' tbl[#tbl+1] = string.format('%s', page, name) end h.na_or_val(tr, table.concat(tbl, ' ')) end, order = 19001, sort_type = 'text', },   {        arg = {'boss', 'boss_number'}, header = i18n.item_table.boss_number, fields = {'maps.area_id'}, display = function(tr, data, na, results2) if results2['boss_query'] == nil then results2['boss_query'] = m_cargo.query(                   {'items', 'maps', 'areas', 'monsters', 'main_pages'},                    {                        'items._pageName',                        'maps.area_id',                        'areas.id',                        'areas.boss_monster_ids',                        'monsters._pageName',                        'monsters.name',                        'main_pages._pageName',                    },                    {                        join=                            items._pageID=maps._pageID,                            maps.area_id=areas.id,                            areas.boss_monster_ids HOLDS monsters.metadata_id,                            monsters.metadata_id=main_pages.id                       ,                        where=string.format(                            items._pageID IN (%s)                            AND maps.area_id IS NOT NULL                            AND areas.boss_monster_ids HOLDS LIKE "%%"                        , table.concat(results2.pageIDs, ', ') ),                       orderBy='areas.boss_monster_ids',                    }                )

results2['boss_query'] = m_cargo.map_results_to_id{ results=results2['boss_query'], field='items._pageName', }           end local results = results2['boss_query'][data['items._pageName']] or {} local tbl = {} for _,v in ipairs(results) do               tbl[#tbl+1] = v['areas.boss_monster_ids'] end h.na_or_val(tr, #tbl) end, order = 19002, },   {        arg = {'legacy'}, header = i18n.item_table.legacy, fields = {'items.name'}, display = function(tr, data, na, results2) if results2['legacy_query'] == nil then results2['legacy_query'] = m_cargo.query(                   {'items', 'legacy_variants'},                    {                        'items._pageID',                        'items._pageName',                        'items.frame_type',                        'legacy_variants.removal_version',                        'legacy_variants.implicit_stat_text',                        'legacy_variants.explicit_stat_text',                        'legacy_variants.stat_text',                        'legacy_variants.base_item',                        'legacy_variants.required_level'                    },                    {                        join='items._pageID=legacy_variants._pageID',                        where='legacy_variants.removal_version IS NOT NULL',                        where=string.format( 'items._pageID IN (%s) AND legacy_variants.removal_version IS NOT NULL', table.concat(results2.pageIDs, ', ') ),                       orderBy='items._pageName',                    }                )

results2['legacy_query'] = m_cargo.map_results_to_id{ results=results2['legacy_query'], field='items._pageName', }           end local results = results2['legacy_query'][data['items._pageName']] or {}

local tbl = mw.html.create('table') :attr('width', '100%') for _, v in ipairs(results) do               local cell = {} local l = { 'legacy_variants.base_item', 'legacy_variants.stat_text' }

-- Clean up data: for _, k in ipairs(l) do                   if v[k] ~= nil then local s = m_util.string.split(v[k], '*') local s_flt = {} for _, sss in ipairs(s) do                           if sss ~= nil and sss ~= '' then s_flt[#s_flt+1] = string.gsub(sss, '\n', '') end end

cell[#cell+1] = table.concat(s_flt, ' ') end end

local sep = string.format(                   ' ',                    v['items.frame_type']                ) tbl :tag('tr') :attr('class', 'upgraded-from-set') :tag('td') :wikitext(                               v['legacy_variants.removal_version']                            ) :done :tag('td') :attr('class', 'group legacy-stats plainlist') :wikitext(table.concat(cell, sep)) :done :done end tr               :tag('td') :node(tbl) end, order = 21001, sort_type = 'text', },   {        arg = {'granted_skills'}, header = i18n.item_table.granted_skills, fields = {'items.name'}, display = function(tr, data, na, results2) if results2['granted_skills_query'] == nil then results2['granted_skills_query'] = m_cargo.query(                   {'items', 'item_mods', 'mods', 'skill', 'items=items2'},                    {                        'items._pageName',                        'items.name',                        'item_mods.id',                        'mods._pageName',                        'mods.id',                        'mods.granted_skill',                        'mods.stat_text_raw',                        'skill._pageName',                        'skill.skill_id',                        'skill.active_skill_name',                        'skill.skill_icon',                        'skill.stat_text',                        'items2.class',                        'items2.name',                        'items2.inventory_icon',                        'items2.size_x',                        'items2.size_y',                        'items2.html', },                   {                        join='items._pageID=item_mods._pageID, item_mods.id=mods.id, mods.granted_skill=skill.skill_id, skill._pageID=items2._pageID', where=string.format(                           'items._pageID IN (%s) AND mods.granted_skill IS NOT NULL',                            table.concat(results2.pageIDs, ', ')                        ), }               )

results2['granted_skills_query'] = m_cargo.map_results_to_id{ results=results2['granted_skills_query'], field='items._pageName', }           end local results = results2['granted_skills_query'][data['items._pageName']] or {}

local tbl = {} for _, v in ipairs(results) do

-- Check if a level for the skill is specified in the -- mod stat text. -- Stat ids have unreliable naming convention so using -- the mod stat text instead. local level = '' local stat_text = v['mods.stat_text_raw'] or '' local level_number = string.match(                   stat_text:lower,                    m_util.string.format( i18n.item_table.granted_skills_level_pattern, {                           granted_skills_level_label = i18n.item_table.granted_skills_level_label:lower }                   )                )

-- If a level number was specified in the stat text -- then add it to the cell: if level_number then level = m_util.string.format(                       i18n.item_table.granted_skills_level_format,                        {                            granted_skills_level_label = i18n.item_table.granted_skills_level_label,                            level_number = level_number,                        }                    ) end

-- Use different formats depending on if it's a gem or               -- not: if v['items2.class'] == nil then tbl[#tbl+1] = m_util.string.format(                       i18n.item_table.granted_skills_skill_output_format,                        {                            level = level,                            sl = f_skill_link{                                skip_query=true,                                page = v['skill.active_skill_name']                                    or v['skill._pageName']                                    or v['mods._pageName']                                    or '',                                name = v['skill.active_skill_name']                                    or v['skill.stat_text']                                    or v['mods.granted_skill'],                                icon = v['skill.skill_icon'],                            },                        }                    ) else local il_args = { skip_query=true, page=v['items2._pageName'], name=v['items2.name'], inventory_icon=v['items2.inventory_icon'], width=v['items2.size_x'], height=v['items2.size_y'], }

-- TODO: add in tpl_args. if no_html == nil then il_args.html = v['items2.html'] end tbl[#tbl+1] = m_util.string.format(                       i18n.item_table.granted_skills_gem_output_format,                        {                            level = level,                            il = f_item_link(il_args),                        }                    ) end end h.na_or_val(tr, table.concat(tbl, ' ')) end, order = 22001, sort_type = 'text', },   {        arg = 'alternate_art', header = i18n.item_table.alternate_art, fields = {'items.alternate_art_inventory_icons'}, display = function (tr, data) local alt_art = m_util.string.split(               data['items.alternate_art_inventory_icons'],                ','            )

-- TODO: Use il instead to handle size? -- local size = 39 local out = {} for i,v in ipairs(alt_art) do               out[#out+1] = string.format(                    'link=|%s',                    v,                    v                ) end

tr               :tag('td') :wikitext(table.concat(out, '')) end, order = 23000, sort_type = 'text', }, }

data_map.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 = {'skill.skill_icon'}, display = h.tbl.display.factory.value{options = { [1] = {               fmt='%s', },       }},        order = 1001, sort_type = 'text', },   {        arg = {'stat', 'stat_text'}, header = i18n.item_table.stats, fields = {'skill.stat_text'}, display = h.tbl.display.factory.value{}, order = 2000, sort_type = 'text', },   {        arg = {'quality', 'quality_stat_text'}, header = i18n.item_table.quality_stats, fields = {'skill.quality_stat_text'}, display = h.tbl.display.factory.value{}, order = 2001, sort_type = 'text', },   {        arg = 'description', header = i18n.item_table.description, fields = {'skill.description'}, display = h.tbl.display.factory.value{}, order = 2100, sort_type = 'text', },   {        arg = 'level', header = m_game.level_requirement.icon, fields = h.tbl.range_fields{field='items.required_level'}, display = h.tbl.display.factory.range{field='items.required_level'}, 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%%', skill_levels = true, },       }},        order = 4000, options = { [1] = {               skill_levels = true, },       },    },    {        arg = 'cast_time', header = i18n.item_table.cast_time, fields = {'skill.cast_time'}, display = h.tbl.display.factory.value{options = { }},       order = 4001, options = { },   },    {        arg = {'aspd', 'attack_speed', 'attack_speed_multiplier'}, header = i18n.item_table.attack_speed_multiplier, fields = {'skill_levels.attack_speed_multiplier'}, display = h.tbl.display.factory.value{options = { [1] = {               fmt='%s%%', skill_levels = true, },       }},        order = 4002, options = { [1] = {               skill_levels = true, },       },    },    {        arg = 'dmgeff', header = i18n.item_table.damage_effectiveness, fields = {'skill_levels.damage_effectiveness'}, display = h.tbl.display.factory.value{options = { [1] = {               fmt='%s%%', skill_levels = true, },       }},        order = 4003, options = { [1] = {               skill_levels = true, },       },    },    {        arg = 'mcm', header = i18n.item_table.mana_cost_multiplier, fields = {'skill_levels.mana_multiplier'}, display = h.tbl.display.factory.value{options = { [1] = {               fmt='%s%%', skill_levels = true, },       }},        order = 5000, options = { [1] = {               skill_levels = true, },       },    },    {        arg = 'mana', header = i18n.item_table.mana_cost, fields = {'skill_levels.mana_cost', 'skill.has_percentage_mana_cost', 'skill.has_reservation_mana_cost'}, display = function (tr, data, fields, data2) local appendix = '' if m_util.cast.boolean(data['skill.has_percentage_mana_cost']) then appendix = appendix .. '%%'           end if m_util.cast.boolean(data['skill.has_reservation_mana_cost']) then appendix = appendix .. ' ' .. i18n.item_table.reserves_mana_suffix end

h.tbl.display.factory.value{options = { [1] = {                   fmt='%d' .. appendix, skill_levels = true, },           }}(tr, data, {'skill_levels.mana_cost'}, data2) end, order = 5001, options = { [1] = {               skill_levels = true, },       },    },    {        arg = 'vaal', header = i18n.item_table.vaal_souls_requirement, fields = {'skill_levels.vaal_souls_requirement'}, display = h.tbl.display.factory.value{options = { [1] = {               skill_levels = true, },       }},        order = 6000, options = { [1] = {               skill_levels = true, },       },    },    {        arg = 'vaal', header = i18n.item_table.stored_uses, fields = {'skill_levels.vaal_stored_uses'}, display = h.tbl.display.factory.value{options = { [1] = {               skill_levels = true, },       }},        order = 6001, options = { [1] = {               skill_levels = true, },       },    },    {        arg = 'radius', header = i18n.item_table.primary_radius, fields = {'skill.radius', 'skill.radius_description'}, options = {[2] = {optional = true}}, display = function (tr, data) tr               :tag('td') :attr('data-sort-value', data['skill.radius']) :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_description'}(nil, nil, data['skill.radius'])) end, order = 7000, },   {        arg = 'radius', header = i18n.item_table.secondary_radius, fields = {'skill.radius_secondary', 'skill.radius_secondary_description'}, options = {[2] = {optional = true}}, display = function (tr, data) tr               :tag('td') :attr('data-sort-value', data['skill.radius_secondary']) :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_secondary_description'}(nil, nil, data['skill.radius_secondary'])) end, order = 7001, },   {        arg = 'radius', header = i18n.item_table.tertiary_radius, fields = {'skill.radius_tertiary', 'skill.radius_tertiary_description'}, options = {[2] = {optional = true}}, display = function (tr, data) tr               :tag('td') :attr('data-sort-value', data['skill.radius_tertiary']) :wikitext(h.tbl.display.factory.descriptor_value{tbl=data, key='skill.radius_tertiary_description'}(nil, nil, data['skill.radius_tertiary'])) end, order = 7002, }, }

for i, attr in ipairs(m_game.constants.attribute_order) do   local attr_data = m_game.constants.attributes[attr] table.insert(data_map.generic_item, 7, {       arg = attr_data.arg,        header = attr_data.icon,        fields = h.tbl.range_fields{field=string.format('items.required_%s', attr)},        display = h.tbl.display.factory.range{field=string.format('items.required_%s', attr)},        order = 5000+i,    }) table.insert(data_map.skill_gem_new, 1, {       arg = attr_data.arg,        header = attr_data.icon,        fields = {string.format('skill_gems.%s_percent', attr)},        display = function (tr, data)            tr                :tag('td')                    :attr('data-sort-value', data[string.format('skill_gems.%s_percent', attr)])                    :wikitext('')        end,        order = 3000+i,    }) end

-- -- Invoke callables --

local p = {}

-- -- Template:Item table --

function p.item_table(frame) --[[   Creates a generic table for items.

Examples = p.item_table{ q_tables='vendor_rewards, quest_rewards, skill_gems', q_join='items._pageID=vendor_rewards._pageID, items._pageID=quest_rewards._pageID, items._pageID=skill_gems._pageID', q_where='(items.class="Active Skill Gems" OR items.class = "Support Skill Gems") AND (vendor_rewards.quest_id IS NOT NULL OR quest_rewards.quest_id IS NOT NULL)', vendor=1, }   ]]

local t = os.clock -- args local tpl_args = getArgs(frame, {           parentFirst = true        }) frame = m_util.misc.get_frame(frame)

tpl_args.q_where = m_cargo.replace_holds{string=tpl_args.q_where}

local modes = { skill = { data = data_map.skill_gem_new, header = i18n.item_table.skill_gem, },       item = { data = data_map.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 results2 = { stats = {}, skill_levels = {}, pageIDs = {}, }

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 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 = (results2.stats[data['items._pageName']] or {})[stat_info.id] if stat ~= nil then stat_texts[#stat_texts+1] = m_util.html.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 = (results2.stats[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, m_util.html.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._pageID', 'items._pageName', 'items.name', 'items.inventory_icon', 'items.html', 'items.size_x', 'items.size_y', }

--   local prepend = { 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

local skill_levels = {} 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 {} if rowinfo.options[index].skill_levels then skill_levels[#skill_levels+1] = field else fields[#fields+1] = field tables_assoc[m_util.string.split(field, '%.')[1]] = true end end end

if #skill_levels > 0 then fields[#fields+1] = 'skill.max_level' tables_assoc.skill = true

end

-- Reformat the tables and fields so they can be retrieved correctly: local tables = {} for table_name,_ in pairs(tables_assoc) do       tables[#tables+1] = table_name end local tbls = table.concat(tables,',') .. (query.tables or '') query.tables = m_util.string.split(tbls, ',')

for index, field in ipairs(fields) do       fields[index] = string.format('%s=%s', field, field) end query.fields = fields

-- Take care of the minimum required joins, joins from templates -- must still be userdefined: 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 if #joins > 0 and query.join then query.join = table.concat(joins, ',') .. ',' .. query.join elseif #joins > 0 and not query.join then query.join = table.concat(joins, ',') elseif #joins == 0 and query.join then -- leave query.join as is   end

-- Needed to eliminate duplicates supplied via table joins: query.groupBy = 'items._pageID' .. (query.groupBy or '')

-- Query results: local results = m_cargo.query(query.tables, query.fields, query)

if #results == 0 and tpl_args.default ~= nil then return tpl_args.default end

if #results > 0 then -- Create a list of found pageIDs for column specific queries: for _,v in ipairs(results) do           results2.pageIDs[#results2.pageIDs+1] = v['items._pageID'] end

-- fetch skill level information if #skill_levels > 0 then skill_levels[#skill_levels+1] = 'skill_levels._pageName' skill_levels[#skill_levels+1] = 'skill_levels.level' local pages = {} for _, row in ipairs(results) do               pages[#pages+1] = string.format('(skill_levels._pageID="%s" AND skill_levels.level IN (0, 1, %s))', row['items._pageID'], row['skill.max_level']) end local temp = m_cargo.query(               {'skill_levels'},                skill_levels,                {                    where=table.concat(pages, ' OR '),                    groupBy='skill_levels._pageID, skill_levels.level',                }            ) -- map to results for _, row in ipairs(temp) do               if results2.skill_levels[row['skill_levels._pageName']] == nil then results2.skill_levels[row['skill_levels._pageName']] = {} end -- TODO: convert to int? results2.skill_levels[row['skill_levels._pageName']][row['skill_levels.level']] = row end end

if #stat_columns > 0 then local pages = {} for _, row in ipairs(results) do               pages[#pages+1] = string.format('item_stats._pageID="%s"', row['items._pageID']) end

local query_stat_ids = {} for stat_id, _ in pairs(query_stats) do               query_stat_ids[#query_stat_ids+1] = string.format('item_stats.id="%s"', stat_id) end

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 = m_cargo.query(               {'items', 'item_stats'},                {'item_stats._pageName', 'item_stats.id', 'item_stats.min', 'item_stats.max', 'item_stats.avg'},                {                    where=string.format('item_stats.is_implicit IS NULL AND (%s) AND (%s)', table.concat(query_stat_ids, ' OR '), table.concat(pages, ' OR ')),                    join='items._pageID=item_stats._pageID',                    -- Cargo workaround: avoid duplicates using groupBy                    groupBy='items._pageID, item_stats.id',                }            )

for _, row in ipairs(temp) do               local stat = { min = tonumber(row['item_stats.min']), max = tonumber(row['item_stats.max']), avg = tonumber(row['item_stats.avg']), }

if results2.stats[row['item_stats._pageName']] == nil then results2.stats[row['item_stats._pageName']] = {[row['item_stats.id']] = stat} else results2.stats[row['item_stats._pageName']][row['item_stats.id']] = stat end end end end

--   -- Display the table --

local tbl = mw.html.create('table') tbl:attr('class', 'wikitable sortable item-table')

-- Headers: local tr = tbl:tag('tr') tr       :tag('th') :wikitext(modes[tpl_args.mode].header) :done for _, row_info in ipairs(row_infos) do       local th = tr:tag('th')

if row_info.colspan then th:attr('colspan', row_info.colspan) end

th           :attr('data-sort-type', row_info.sort_type or 'number') :wikitext(row_info.header) end

-- Rows: 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'], width=row['items.size_x'], height=row['items.size_y'], }

if tpl_args.no_html == nil then il_args.html = row['items.html'] end

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] == nil or row[field] == '' then local opts = rowinfo.options[index] if opts.optional ~= true and opts.skill_levels ~= true then display = false break else row[field] = nil end end end if display then rowinfo.display(tr, row, rowinfo.fields, results2) else tr:wikitext(m_util.html.td.na) end end end

cats = {} if #results == query.limit then cats[#cats+1] = i18n.categories.query_limit end

if #results == 0 then cats[#cats+1] = i18n.categories.no_results end

mw.logObject({os.clock - t, query})

return tostring(tbl) .. m_util.misc.add_category(cats, {ingore_blacklist=tpl_args.debug}) end

--- -- Map item drops ---

function p.map_item_drops(frame) --[[   Gets the area id from the map item and activates    Template:Area_item_drops.

Examples: = p.map_item_drops{page='Underground River Map (War for the Atlas)'} ]]

-- 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 results = m_cargo.query(       {'maps'},        {'maps.area_id'},        {            where=string.format('maps._pageName="%s" AND maps.area_id IS NOT NULL', tpl_args.page),            -- Only need each page name once            groupBy='maps._pageName',        }    ) local id = '' if #results > 0 then id = results[1]['maps.area_id'] end return frame:expandTemplate{ title = 'Area item drops', args = {area_id=id} } end

--- -- Prophecy description ---

function p.prophecy_description(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 results = m_cargo.query(       {'prophecies'},        {'prophecies.objective', 'prophecies.reward'},        {            where=string.format('prophecies._pageName="%s"', tpl_args.page),            -- Only need each page name once            groupBy='prophecies._pageName',        }    )

results = results[1]

local out = {}

if results['prophecies.objective'] then out[#out+1] = string.format(' %s ', i18n.prophecy_description.objective) out[#out+1] = results['prophecies.objective'] end

if results['prophecies.reward'] then out[#out+1] = string.format(' %s ', i18n.prophecy_description.reward) out[#out+1] = results['prophecies.reward'] end

return table.concat(out, '\n') end

-- -- Item disambiguation -- function h.find_aliases(tpl_args) --  This function queries items for an item name, then checks if it has   had any name changes then queries for that name as well.

-- Get initial name: tpl_args.name_list = { tpl_args.name or m_util.string.split(           tostring(mw.title.getCurrentTitle),            ' %(' )   }

-- Query for items with similar name, repeat until no new names are -- found. local n   local results = {} local hash = {} repeat local n_old = #tpl_args.name_list

local where_tbl = {} for _, item_name in ipairs(tpl_args.name_list) do           for _, prefix in ipairs({'', 'Shaped '}) do                where_tbl[#where_tbl+1] = string.format(                    'items__name_list._value = "%s%s"',                    prefix,                    item_name                ) end end local where_str = table.concat(where_tbl, ' OR ')

results = m_cargo.query(       	-- explicitly join to the child list table so that we don't need a REGEXP            { 'items__name_list', 'items', 'maps'},            {                'items._pageName',                'items.name',                -- we need to give this field a different name from `name_list` so that it doesn't conflict                'items.name_list__full=name_list2',                'items.release_version',                'items.removal_version',                'items.drop_enabled',            },            {                join='items__name_list._rowID=items._ID, items._pageName=maps._pageName',                where=where_str,                groupBy='items._pageName',                orderBy='items.release_version DESC, items.removal_version DESC, items.name ASC, maps.area_id ASC',            }        )

-- Filter duplicates: for i,v in ipairs(results) do           local r = m_util.string.split(v['name_list2'], '�') if type(r) == string then r = {r} end

for j,m in ipairs(r) do               if hash[m] == nil then hash[m] = m                   tpl_args.name_list[#tpl_args.name_list+1] = m                end end end until #tpl_args.name_list == n_old

return results end

function p.item_disambiguation(frame) --[[   This function finds that items with a name or has had that name.

To do   - Should text imply which is the original map, even if it isn't (Original)? How to properly sort drop disabled items, with removal version? How to deal with names that have been used multiple times? Terrace Map

Examples = p.item_disambiguation{name='Abyss Map'} = p.item_disambiguation{name='Caldera Map'} = p.item_disambiguation{name='Crypt Map'} = p.item_disambiguation{name='Catacombs Map'} = p.item_disambiguation{name='Harbinger Map (High Tier)'} ]]

-- Get template arguments. local tpl_args = getArgs(frame, {       parentFirst = true    }) frame = m_util.misc.get_frame(frame)

local current_title = tostring(mw.title.getCurrentTitle) -- Get the page name. tpl_args.name = tpl_args.name or m_util.string.split(       current_title,        ' %(' )[1]

-- Query for items with similar name. local results = h.find_aliases(tpl_args)

-- Format the results: local out = {} local container = mw.html.create('div') local tbl = container:tag('ul') for i,v in ipairs(results) do       if v['items._pageName'] ~= current_title then -- Get the content inside the last parentheses: local known_release = string.reverse(               string.match( string.reverse(v['items._pageName']), '%)(.-)%('               )            )

local drop_enabled if known_release == 'Original' then drop_enabled = i18n.item_disambiguation.original elseif m_util.cast.boolean(v['items.drop_enabled']) then drop_enabled = i18n.item_disambiguation.drop_enabled else drop_enabled = i18n.item_disambiguation.drop_disabled end

if known_release ~= 'Original' then known_release = string.format(                   i18n.item_disambiguation.known_release,                    known_release,                    known_release                ) else known_release = '' end

tbl :tag('li') :wikitext(                       string.format( i18n.item_disambiguation.list_pattern, f_item_link{page=v['items._pageName']}, drop_enabled, known_release )                   )        end end out[#out+1] = tostring(container)

-- Add a category when the template uses old template inputs: local old_args = { 'war', 'atlas', 'awakening', 'original', 'heading', 'hide_heading', 'show_current', }   for _,v in ipairs(old_args) do        if tpl_args[v] ~= nil then return table.concat(out, '') .. m_util.misc.add_category(               {'Pages with old template arguments'}            ) end end

return table.concat(out, '') end

function p.simple_item_list(frame) --[[   Creates a simple list of items.

Examples = p.simple_item_list{ q_tables='maps', q_join='items._pageID=maps._pageID', q_where='maps.tier=1 AND items.drop_enabled=1 AND items.rarity_id="normal"', no_html=1, link_from_name=1, }   ]]

-- 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', 'items.class', }

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 tables = m_util.string.split(tpl_args.q_tables or '', ',%s*') table.insert(tables, 'items')

query.groupBy = query.groupBy or 'items._pageID'

local results = m_cargo.query(       tables,        fields,        query    )

local out = {} for _, row in ipairs(results) do       local page if tpl_args.use_name_as_link ~= nil then page = row['items.name'] else page = row['items._pageName'] end

local link = f_item_link{ page=page, name=row['items.name'], inventory_icon=row['items.inventory_icon'] or '', html=row['items.html'] or '', skip_query=true }

if tpl_args.format == nil then out[#out+1] = string.format('* %s', link) elseif tpl_args.format == 'none' then out[#out+1] = link elseif tpl_args.format == 'li' then out[#out+1] = string.format('%s', link) else error(string.format(i18n.errors.generic_argument_parameter, 'format', tpl_args.format)) end end

if tpl_args.format == nil then return table.concat(out, '\n') elseif tpl_args.format == 'none' then return table.concat(out, '\n') elseif tpl_args.format == 'li' then return table.concat(out) end end

-- -- Debug stuff -- p.debug = {}

function p.debug._tbl_data(tbl) keys = {} for _, data in ipairs(tbl) 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(data_map.generic_item) end

function p.debug.skill_gem_all return p.debug._tbl_data(data_map.skill_gem_new) end

-- -- Return --

return p