Path of Exile Wiki

Wiki поддерживается сообществом, поэтому подумайте над тем, чтобы внести свой вклад.

ПОДРОБНЕЕ

Path of Exile Wiki
Path of Exile Wiki
50 302
страницы
(Avoiding magic numbers is one of the most fundamental rules of programming.)
м (Откат правок Ruba159753 (обсуждение) к версии Kordloperdlo)
Метка: откат
Строка 1: Строка 1:
  +
-- Cargo reworked item module
-------------------------------------------------------------------------------
 
--
 
-- Module:Item2
 
--
 
-- This module implements Template:Item and Template:Itembox
 
-------------------------------------------------------------------------------
 
   
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
Строка 37: Строка 32:
 
-- maybe smw
 
-- maybe smw
   
  +
-- ----------------------------------------------------------------------------
 
  +
-- Imports
local getArgs = require('Module:Arguments').getArgs
 
  +
-- ----------------------------------------------------------------------------
 
local m_util = require('Module:Util')
 
local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')
+
local getArgs = require('Module:Arguments').getArgs
  +
local m_game = require('Module:Game')
 
local m_skill = require('Module:Skill')
 
local m_skill = require('Module:Skill')
 
local m_area = require('Module:Area')
 
local m_area = require('Module:Area')
  +
local m_cargo = require('Module:Cargo')
  +
local f_item_link = require('Module:Item link').item_link
   
local m_game = mw.loadData('Module:Game')
+
local p = {}
  +
local c = {}
  +
c.image_size = 39
  +
c.image_size_full = c.image_size * 2
  +
c.query_default = 50
  +
c.query_max = 200
   
  +
c.lang = mw.language.new('en')
-- Should we use the sandbox version of our submodules?
 
local use_sandbox = m_util.misc.maybe_sandbox()
 
   
  +
-- ----------------------------------------------------------------------------
-- Lazy loading
 
  +
-- Strings
local f_item_link -- require('Module:Item link').item_link
 
  +
-- ----------------------------------------------------------------------------
local f_process_upgraded_from -- require('Module:Item2/upgrade').process_upgraded_from
 
  +
-- This section contains strings used by this module.
local f_build_cargo_data -- require('Module:Item2/cargo').build_cargo_data
 
  +
-- 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 = {
  +
inventory_icon = 'File:%s inventory icon.png',
  +
status_icon = 'File:%s status icon.png',
  +
divination_card_art = 'File:%s card art.png',
  +
gem_tag_category = '[[:Категория:%s (тег камня)|%s]]',
  +
  +
misc = {
  +
-- used to identify betrayal map series to avoid showing area infobox.
  +
betrayal = 'Предательство',
  +
upgraded_from_map = 'случайная карта уровнем выше того же региона',
  +
},
   
  +
categories = {
-- The cfg table contains all localisable strings and configuration, to make it
 
  +
-- maintenance cats
-- easier to port this module to another wiki.
 
  +
improper_modifiers = 'Предметы с неправильными модификаторами',
local cfg = use_sandbox and mw.loadData('Module:Item2/config/sandbox') or mw.loadData('Module:Item2/config')
 
  +
missing_release_version = 'Предметы без версии выпуска',
  +
broken_upgraded_from_reference = 'Предметы со сломанными ссылками предметов в параметрах "upgraded_from"',
  +
duplicate_upgraded_from_reference = 'Предметы с повторяющимися наборами в параметрах "upgraded_from"',
  +
duplicate_query_area_ids = 'Предметы с повторяющимися идентификаторами областей из запросов',
  +
base_items = 'Базовые предметы',
  +
derived_items = 'Производные предметы',
  +
sell_prices_override = 'Предметы с изменёнными ценами продаж',
   
  +
-- regular cats
local i18n = cfg.i18n
 
  +
alternate_artwork = 'Предметы с альтернативным внешним видом',
   
  +
-- misc
local core = use_sandbox and require('Module:Item2/core/sandbox') or require('Module:Item2/core')
 
  +
gem_tag_affix = '%s (тег камня)',
  +
unique_affix = 'Уникальные %s',
  +
  +
prophecies = 'Пророчества',
  +
blight_item = 'Масла',
  +
talismans = 'Талисманы',
  +
essences = 'Сущности',
  +
},
   
  +
stat_skip_patterns = {
local c = {}
 
c.item_classes = {}
+
maps = {
  +
'%d+%% увеличение количества найденных предметов в этой области',
  +
'%d+%% повышение редкости найденных предметов в этой области',
  +
'%+%d+%% увеличение размера групп монстров',
  +
-- ranges
  +
'%(%d+%-%d+%)%% увеличение количества найденных предметов в этой области',
  +
'%(%d+%-%d+%)%% повышение редкости найденных предметов в этой области',
  +
'%+%(%d+%-%d+%)%% увеличение размера групп монстров',
  +
},
  +
jewels = {
  +
'Limited to %d+ %(Hidden%)',
  +
'Jewel has a radius of %d+ %(Hidden%)',
  +
},
  +
},
  +
  +
  +
help_text_defaults = {
  +
active_gem = 'Для получения умения выберите предмет и поместите камень в гнездо соответствующего цвета. Щелкните ПКМ, чтобы вынуть камень из гнезда.',
  +
support_gem = 'Это камень поддержки. Он дает преимущества не герою, а камню умения в связанных гнездах. Вставьте камень поддержки в гнездо, соединенное с гнездом усиливаемого камня активного умения. Щелкните ПКМ, чтобы вынуть камень из гнезда.',
  +
jewel = 'Поместите самоцвет в доступное гнездо на дереве пассивных умений. Чтобы вынуть самоцвет, щёлкните по нему правой кнопкой мыши.',
  +
},
  +
  +
-- texts specific to upgraded from
  +
upgraded_from_tooltips = {
  +
-- formatters
  +
f = {
  +
random = 'случайный %s',
  +
random_2 = 'случайная %s',
  +
random_3 = 'случайное %s',
  +
random_4 = 'случайные %s',
  +
random_corrupted = string.format('случайный %s %%s', m_util.html.poe_color('corrupted', 'осквернённый')),
  +
random_corrupted_2 = string.format('случайная %s %%s', m_util.html.poe_color('corrupted', 'осквернённая')),
  +
random_corrupted_3 = string.format('случайное %s %%s', m_util.html.poe_color('corrupted', 'осквернённое')),
  +
random_corrupted_4 = string.format('случайные %s %%s', m_util.html.poe_color('corrupted', 'осквернённые')),
  +
random_unidentified_corrupted = string.format('случайный %s %%s', m_util.html.poe_color('corrupted', 'неопознанный осквернённый')),
  +
random_unidentified_corrupted_2 = string.format('случайная %s %%s', m_util.html.poe_color('corrupted', 'неопознанная осквернённая')),
  +
random_two_implicit_corrupted = string.format('случайный %s %%s %s', m_util.html.poe_color('corrupted', 'осквернённый'), m_util.html.poe_color('corrupted', 'с двумя собственными свойствами')),
  +
random_item_level_x = 'случайный %s %s уровня',
  +
random_item_level_x_2 = 'случайная %s %s уровня',
  +
random_item_level_x_3 = 'случайное %s %s уровня',
  +
random_item_level_x_4 = 'случайные %s %s уровня',
  +
random_x_link = 'случайный %s с %s связанными гнёздами',
  +
random_x_link_item_level_y = 'случайный %s с %s связанными гнёздами %s уровня',
  +
random_x_link_item_level_y_random_influenced = 'случайный %s с %s связанными гнёздами %s уровня и случайным влиянием',
  +
random_x_amount = 'случайный %s (%s шт.)',
  +
random_x_amount_2 = 'случайная %s (%s шт.)',
  +
random_x_amount_3 = 'случайное %s (%s шт.)',
  +
random_x_amount_4 = 'случайные %s (%s шт.)',
  +
random_influenced = 'случайный предмет %s с влиянием',
  +
random_two_influenced = 'случайная %s с двойным влиянием',
  +
random_two_influenced_x_2 = 'случайная %s с двойным влиянием %s уровня',
  +
random_two_influenced_x_3 = 'случайное %s с двойным влиянием %s уровня',
  +
random_shaper = 'случайный %s с влиянием Создателя',
  +
random_shaper_item_level_x = 'случайный %s с влиянием Создателя %s уровня',
  +
random_shaper_item_level_x_2 = 'случайная %s с влиянием Создателя %s уровня',
  +
random_shaper_item_level_x_3 = 'случайное %s с влиянием Создателя %s уровня',
  +
random_shaper_item_level_x_4 = 'случайные %s с влиянием Создателя %s уровня',
  +
random_shaper_hunter_item_level_x = 'случайный %s с влиянием Создателя и Охотника %s уровня',
  +
random_shaper_hunter_item_level_x_2 = 'случайная %s с влиянием Создателя и Охотника %s уровня',
  +
random_shaper_hunter_item_level_x_3 = 'случайное %s с влиянием Создателя и Охотника %s уровня',
  +
random_shaper_hunter_item_level_x_4 = 'случайные %s с влиянием Создателя и Охотника %s уровня',
  +
random_elder_item_level_x = 'случайный %s с влиянием Древнего %s уровня',
  +
random_elder_item_level_x_2 = 'случайная %s с влиянием Древнего %s уровня',
  +
random_elder_item_level_x_3 = 'случайное %s с влиянием Древнего %s уровня',
  +
random_elder_item_level_x_4 = 'случайные %s с влиянием Древнего %s уровня',
  +
random_eternal_labyrinth_enchantment = string.format('случайный %%s со случайным %s', m_util.html.poe_color('enchanted', 'зачарованием Вечного Лабиринта')),
  +
random_veiled = 'случайный %s с завуалированным свойством',
  +
random_two_veiled = 'случайный %s с двумя завуалированными свойствами',
  +
random_primordial = string.format('случайный %s %%s', m_util.html.poe_color('magic', 'Первородный')),
  +
random_map_level = 'случайная %s, осквернённая и %s уровня',
  +
  +
tier_x_map = 'карта %s уровня',
  +
level_x_gem = 'камень %s уровня',
  +
level_x_y_gem = 'камень с меткой "%s" %s уровня',
  +
superior_gem_q_x = 'камень с качеством %s',
  +
superior_x_gem_q_y = 'камень с меткой "%s" и качеством %s',
  +
superior_level_x_y_gem_q_z = 'камень с меткой "%s" %s уровня и качеством %s',
  +
superior_skill_gem = 'камень умения случайного качества',
  +
superior_level_x_skill_gem_q_y = 'камень умения %s уровня и качеством %s',
  +
superior_level_x_support_plus_gem_q_y = 'пробужденный камень поддержки %s уровня и качеством %s',
  +
  +
x_item = 'предмет %s',
  +
},
  +
  +
-- items / rewards not covered by module game
  +
agate_amulet = 'амулет с агатом',
  +
unset_ring = 'кольцо без камня',
  +
gold_ring = 'золотое кольцо',
  +
prismatic_ring = 'радужное кольцо',
  +
two_stone_ring = 'кольцо с двумя камнями',
  +
crystal_sceptre = 'кристальный скипетр',
  +
nightmare_bascinet = 'кошмарный бацинет',
  +
rustic_sash = 'кушак',
  +
talisman = 'талисман',
  +
tier_1_talisman = 'талисман 1 уровня',
  +
breachstone_splinter = 'осколок Разлома',
  +
breachstone = 'камень Разлома',
  +
pure_breachstone = 'очищенный камень Разлома',
  +
scarab = 'скарабей',
  +
gilded_scarab = 'золочёный скарабей',
  +
divination_card = 'гадальная карта',
  +
quality_currency = 'валюта, повышающая качество',
  +
essence = 'сущность',
  +
deafening_essence = 'оглушающая сущность',
  +
shrieking_essence = 'визжащая сущность',
  +
fossil = 'ископаемое',
  +
fragment = 'фрагмент',
  +
sacrifice_fragment = 'фрагмент жертвы',
  +
mortal_fragment = 'смертный фрагмент',
  +
uber_elder_fragment = 'фрагмент убер-Древнего',
  +
harbinger_fragment = 'фрагмент Предвестника',
  +
body_armour = 'нательный доспех',
  +
shield = 'щит',
  +
map = 'карта',
  +
belt = 'пояс',
  +
armour = 'доспех',
  +
helmet = 'шлем',
  +
weapon = 'оружие',
  +
jewel = 'самоцвет',
  +
timeless_jewel = 'вневременной самоцвет',
  +
jewellery = 'бижутерия',
  +
one_hand_weapon = 'одноручное оружие',
  +
two_hand_weapon = 'двуручное оружие',
  +
sword = 'меч',
  +
axe = 'топор',
  +
flask = 'флакон',
  +
granite_flask = 'гранитный флакон',
  +
amulet = 'амулет',
  +
ring = 'кольцо',
  +
league_specific_item = 'предмет из лиги',
  +
item = 'предмет',
  +
fated_item = 'пророчество на судьбоносный уникальный предмет',
  +
  +
-- prefix
  +
life = 'со здоровьем',
  +
  +
--
  +
-- used within essences
  +
--
  +
essence_plus_one_level = string.format('+1 уровень в результате %s' ,m_util.html.poe_color('corrupted', 'осквернения')),
  +
essence_type_change = string.format('изменение типа в результате %s' ,m_util.html.poe_color('corrupted', 'осквернения')),
  +
},
  +
  +
-- Used by the item info box
  +
tooltips = {
  +
corrupted = m_util.html.poe_color('corrupted', 'Осквернено'),
  +
support_icon = 'Изображение: %s',
  +
radius = 'Радиус: %s',
  +
mana_reserved = 'Удержано маны: %s',
  +
mana_cost = 'Расход маны: %s',
  +
mana_multiplier = 'Множитель маны: %s',
  +
vaal_souls_per_use = 'Разовый расход душ: %s',
  +
stored_uses = 'Максимум зарядов: %s',
  +
vaal_soul_gain_prevention_time = 'Нельзя получать души: %s',
  +
cooldown_time = 'Перезарядка: %s',
  +
cast_time = 'Время применения: %s',
  +
critical_strike_chance = 'Шанс критического удара: %s',
  +
attack_speed_multiplier = 'Скорость атаки: %s',
  +
attack_speed_multiplier_fmt = '%i%% от базовой',
  +
damage_effectiveness = 'Эффективность добавленного урона: %s',
  +
projectile_speed = 'Скорость снаряда: %s',
  +
quality = 'Качество: %s',
  +
physical_damage = 'Физический урон: %s',
  +
elemental_damage = 'Урон от стихий: %s',
  +
chaos_damage = 'Урон хаосом: %s',
  +
attacks_per_second = 'Атак в секунду: %s',
  +
weapon_range = 'Дальность оружия: %s',
  +
map_level = 'Уровень области: %s',
  +
map_tier = 'Уровень карты: %s',
  +
map_guild_character = m_util.html.abbr('Редактор тега гильдии', 'При редактировании тега гильдии эта карта может быть использована для данного символа') .. ': %s',
  +
item_quantity = 'Количество предметов: %s',
  +
item_rarity = 'Редкость предметов: %s',
  +
monster_pack_size = 'Размер групп монстров: %s',
  +
limited_to = 'Максимум: %s',
  +
flask_mana_recovery = 'Восстанавливает маны: %s за %s сек.',
  +
flask_life_recovery = 'Восстанавливает здоровья: %s за %s сек.',
  +
flask_duration = 'Длится %s сек.',
  +
flask_charges_per_use = 'Расходует %s из %s зарядов при использовании',
  +
chance_to_block = 'Шанс заблокировать удар: %s',
  +
armour = 'Броня: %s',
  +
evasion = 'Уклонение: %s',
  +
energy_shield = 'Энерг. щит: %s',
  +
movement_speed = 'Скорость передвижения: %s',
  +
talisman_tier = 'Уровень талисмана: %s',
  +
stack_size = 'Размер стопки: %s',
  +
essence_level = 'Уровень сущности: %s',
  +
blight_item_tier = 'Уровень масла: %s',
  +
requires = 'Требуется %s',
  +
level_inline = 'Уровень %s',
  +
level = 'Уровень: %s',
  +
gem_quality = 'За 1% качества:',
  +
gem_quality_1 = 'По умолчанию',
  +
gem_quality_2 = 'Аномальный',
  +
gem_quality_3 = 'Искривлённый',
  +
gem_quality_4 = 'Фантомный',
  +
variation_singular = 'Изменений:',
  +
variation_plural = 'Изменений:',
  +
favour_cost = 'Цена расположения: %s',
  +
seal_cost = 'Стоимость печати: <br>%s',
  +
cannot_be_traded_or_modified = 'Этот предмет нельзя отдать или изменить',
  +
  +
-- harvest seeds
  +
  +
seed_tier = 'Уровень семени: %s',
  +
seed_monster = 'При сборе появляется монстр ? уровня',
  +
seed_lifeforce_gained = 'При сборе даёт ? едениц(-ы) чистой %s жизненной силы',
  +
seed_growth_cycles = 'Можно собрать после %s циклов роста',
  +
  +
seed_lifeforce_consumed = 'Потребляет (%s%%) единиц(-ы) конденсированной %s жизненной силы из распылителя за цикл роста',
  +
seed_required_seeds = 'Для роста требует соседства %s %s растений как минимум %s уровня',
  +
  +
-- harvest plant boosters, not used at the moment
  +
plant_booster_extra_chances = 'Seeds in radius give the rarest of %s chosen Crafting Options when Harvested',
  +
plant_booster_lifeforce = 'Seeds in radius produce %s more Lifeforce when Harvested',
  +
plant_booster_additional_crafting_optioons = 'Seeds in radius have %s chance to generate additional Crafting Options when Harvested',
  +
  +
  +
-- heist
  +
heist_required_npc = 'Этот предмет могут использовать: %s',
  +
heist_required_job = 'Требуется %s %s уровня',
  +
heist_any_job = 'Любая роль',
  +
  +
-- ClientStrings.dat -> WeaponClassDisplayName.*
  +
item_class_map = {
  +
['Staff'] = 'Посох',
  +
['Bow'] = 'Лук',
  +
['Wand'] = 'Жезл',
  +
['Two Hand Axe'] = 'Двуручный топор',
  +
['Two Hand Sword'] = 'Двуручный меч',
  +
['Two Hand Mace'] = 'Двуручная булава',
  +
['Warstaff'] = 'Воинский посох',
  +
['Sceptre'] = 'Скипетр',
  +
['One Hand Mace'] = 'Одноручная булава',
  +
['One Hand Axe'] = 'Одноручный топор',
  +
['One Hand Sword'] = 'Одноручный меч',
  +
['Thrusting One Hand Sword'] = 'Одноручный меч',
  +
['Claw'] = 'Когти',
  +
['Dagger'] = 'Кинжал',
  +
['Rune Dagger'] = 'Рунический кинжал',
  +
['FishingRod'] = 'Удочка',
  +
['HideoutDoodad'] = 'Предмет для убежища',
  +
},
  +
  +
random_mod = '&lt;&lt;случайное свойство %s&gt;&gt;',
  +
  +
--
  +
-- secondary infobox
  +
--
  +
extra_info = 'Дополнительная информация',
  +
header_overall = 'Диаграмма улучшения области',
  +
header_upgrades = 'Уровень улучшения',
  +
header_map_tier = 'Уровень карты',
  +
header_map_level = 'Уровень области',
  +
header_connections = 'Соединения',
  +
  +
drop_restrictions = 'Получение',
  +
league_restriction = m_util.html.abbr('Лига(-и):', 'Предмет может быть получен только в Лиге(-ах)') .. ' %s',
  +
drop_disabled = 'НЕ ВЫПАДАЕТ',
  +
  +
purchase_costs = m_util.html.abbr('Стоимость покупки', 'Стоимость покупки предмета такого типа у торгующих NPC. Это не означает, действительно ли NPC продаст вам этот предмет.'),
  +
sell_price = m_util.html.abbr('Цена продажи', 'Товары или валюта, полученные при продаже этого товара у торгующих NPC. Некоторые рецепты торговцев могут переопределить это значение.'),
  +
  +
damage_per_second = 'УВС Оружия',
  +
physical_dps = m_game.constants.damage_types.physical.short_upper,
  +
fire_dps = m_game.constants.damage_types.fire.short_upper,
  +
cold_dps = m_game.constants.damage_types.cold.short_upper,
  +
lightning_dps = m_game.constants.damage_types.lightning.short_upper,
  +
chaos_dps = m_game.constants.damage_types.chaos.short_upper,
  +
elemental_dps = 'Стихийный',
  +
poison_dps = 'Физ+Хаос',
  +
dps = 'Общий',
  +
  +
misc = 'Прочее',
  +
item_class = 'Класс предмета: %s',
  +
metadata_id = 'Metadata ID: %s',
  +
},
  +
  +
item_class_infobox = {
  +
page = '[[Класс предметов]]',
  +
info = m_util.html.abbr('(?)', 'Классы предметов помогают классифицировать предметы. Классы в основном используются для разграничения предметов или камней умений по определенному классу или для создания фильтров предметов.'),
  +
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 (value: %s)',
  +
upgraded_from_duplicate = 'Automatic upgraded from entry is duplicated on page in upgraded_from_set%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.',
  +
base_item_not_found = 'Базовый предмет не найден в базе данных. Проверьте орфографические ошибки или наличие страницы базового предмета на вики. Если страница базового предмета существует на вики, но не может быть найдена, пожалуйста, отредактируйте страницу.',
  +
invalid_league = '%s не распознаётся как "лига"',
  +
invalid_tag = '%s не является допустимым тегом',
  +
generic_argument_parameter = 'Unrecognized %s parameter "%s"',
  +
generic_required_parameter = 'Parameter "%s" must be specified for this item type.',
  +
non_unique_flag = 'Only unique items can are egible for the flag "%s".',
  +
duplicate_area_id_from_query = 'Query found duplicate area ids that do not need to be set on the item. Duplicate ids: "%s"',
  +
duplicate_metadata = 'Дубликат metadata_id "%s" на странице "%s"',
  +
invalid_influence = 'The influence "%s" is invalid. Acceptable values are "shaper", "elder", "crusader", "redeemer", "hunter" and "warlord".',
  +
invalid_class = 'The item class name "%s" is invalid.',
  +
invalid_class_id = 'Данный class_id не верный "%s". Для правильной работы модуля Item2 и шаблона, необходимо использовать правильный идентификатор.',
  +
invalid_rarity_id = 'Недопустимый rarity_id "%s". Допустимые значения: "normal", "magic", "rare" и "unique".',
  +
invalid_region_upgrade_count = 'atlas_connection%s_tier: Invalid amount (%s) of connections, only 5 are allowed',
  +
},
  +
}
   
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
-- Helper functions
+
-- Other stuff
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
   
Строка 78: Строка 422:
 
end
 
end
   
  +
function h.na_or_val(tr, value, func)
-- Lazy loading for Module:Item link
 
  +
if value == nil then
function h.item_link(args)
 
  +
tr:wikitext(m_util.html.td.na())
if not f_item_link then
 
  +
else
f_item_link = require('Module:Item link').item_link
 
  +
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 = nil,
  +
},
  +
{
  +
field = '_range_maximum',
  +
type = nil,
  +
},
  +
{
  +
field = '_range_average',
  +
type = nil,
  +
},
  +
{
  +
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 = m_util.html.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'] = m_util.html.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
  +
  +
-- ----------------------------------------------------------------------------
  +
-- core
  +
-- ----------------------------------------------------------------------------
  +
  +
local core = {}
  +
  +
function core.build_cargo_data(tpl_args, frame)
  +
for _, table_name in ipairs(core.item_classes[tpl_args.class_id].tables) do
  +
-- Need to clone the table here because we'll be changing the table as we run though
  +
-- TODO: Optimize this?
  +
for k, _ in pairs(mw.clone(core.cargo[table_name].fields)) do
  +
field_data = core.cargo[table_name].fields[k]
  +
field_data.table = table_name
  +
for _, stat_data in pairs(core.stat_map) do
  +
if stat_data.field == 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,
  +
-- if the type is nil, use the parent type
  +
-- this is set integer/float values correctly
  +
type = range_field.type or field_data.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,
  +
-- dps values are floating points
  +
type = range_field.type or 'Float',
  +
}
  +
  +
core.cargo[table_name].fields[field_name] = data
  +
core.map[field_name] = data
  +
end
  +
end
  +
end
  +
end
 
end
 
end
return f_item_link(args)
 
 
end
 
end
   
function h.validate_mod(tpl_args, frame, args)
+
function core.validate_mod(tpl_args, frame, args)
 
-- args:
 
-- args:
 
-- key - implict or explicit
 
-- key - implict or explicit
Строка 93: Строка 588:
 
local prefix = args.key .. args.i
 
local prefix = args.key .. args.i
 
local value = tpl_args[prefix]
 
local value = tpl_args[prefix]
local is_implicit = args.key == 'implicit'
 
 
local out = {
 
local out = {
 
result=nil,
 
result=nil,
Строка 99: Строка 593:
 
id=nil,
 
id=nil,
 
stat_text=nil,
 
stat_text=nil,
is_implicit=is_implicit,
+
is_implicit=(args.key == 'implicit'),
 
is_random=nil,
 
is_random=nil,
 
}
 
}
 
 
local mods_table = is_implicit and tpl_args._defined_implicit_mods or tpl_args._mods
 
 
if value ~= nil then
 
if value ~= nil then
 
out.id = value
 
out.id = value
 
out.stat_text = tpl_args[prefix .. '_text']
 
out.stat_text = tpl_args[prefix .. '_text']
 
out.is_random = false
 
out.is_random = false
table.insert(mods_table, out)
+
table.insert(tpl_args._mods, out)
 
 
 
return true
 
return true
Строка 115: Строка 608:
 
value = m_util.string.split(tpl_args[prefix .. '_random_list'], ',%s*')
 
value = m_util.string.split(tpl_args[prefix .. '_random_list'], ',%s*')
 
for _, mod_id in ipairs(value) do
 
for _, mod_id in ipairs(value) do
table.insert(mods_table, {
+
table.insert(tpl_args._mods, {
 
result = nil,
 
result = nil,
 
id = mod_id,
 
id = mod_id,
 
stat_text = tpl_args[prefix .. '_text'] or string.format(i18n.tooltips.random_mod, args.i),
 
stat_text = tpl_args[prefix .. '_text'] or string.format(i18n.tooltips.random_mod, args.i),
is_implicit = is_implicit,
+
is_implicit = (args.key == 'implicit'),
 
is_random = true,
 
is_random = true,
 
})
 
})
Строка 131: Строка 624:
 
out.stat_text = value
 
out.stat_text = value
 
out.is_random = false
 
out.is_random = false
table.insert(mods_table, out)
+
table.insert(tpl_args._mods, out)
 
 
 
return true
 
return true
Строка 139: Строка 632:
 
end
 
end
   
function h.handle_range_args(tpl_args, frame, argument_key, field, value, fmt_options)
+
function core.process_smw_mods(tpl_args, frame)
  +
if #tpl_args._mods > 0 then
fmt_options = mw.clone(fmt_options)
 
fmt_options.return_color = true
 
local html, colour = m_util.html.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'] = m_util.html.format_value(tpl_args, frame, value, fmt_options)
 
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
 
 
--
 
-- Processing
 
--
 
 
function h.process_arguments(tpl_args, frame, params)
 
for _, k in ipairs(params) 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_id and c.item_classes[tpl_args.class_id].defaults ~= nil and c.item_classes[tpl_args.class_id].defaults[k] ~= nil then
 
tpl_args[k] = c.item_classes[tpl_args.class_id].defaults[k]
 
elseif data.default ~= nil then
 
if type(data.default) == 'function' then
 
tpl_args[k] = data.default(tpl_args, frame)
 
else
 
tpl_args[k] = data.default
 
end
 
end
 
end
 
end
 
end
 
 
-- add defaults from class specifics and class groups
 
function h.build_item_classes(tpl_args, frame)
 
core.map.class.func(tpl_args, frame)
 
 
c.item_classes[tpl_args.class_id] = {
 
tables = {},
 
args = {},
 
late_args = {},
 
defaults = {},
 
}
 
 
for _, table_name in ipairs(cfg.tables) do
 
table.insert(c.item_classes[tpl_args.class_id].tables, table_name)
 
end
 
 
local item_classes_extend = {'tables', 'args', 'late_args'}
 
 
for _, row in pairs(cfg.class_groups) do
 
for class, _ in pairs(row.keys) do
 
if class == tpl_args.class_id then
 
for _, k in ipairs(item_classes_extend) do
 
if row[k] ~= nil then
 
for _, value in ipairs(row[k]) do
 
table.insert(c.item_classes[tpl_args.class_id][k], value)
 
end
 
end
 
end
 
break
 
end
 
end
 
end
 
 
local class_specifics = cfg.class_specifics[tpl_args.class_id]
 
if class_specifics then
 
for _, k in ipairs(item_classes_extend) do
 
if class_specifics[k] ~= nil then
 
for _, value in ipairs(class_specifics[k]) do
 
table.insert(c.item_classes[tpl_args.class_id][k], value)
 
end
 
end
 
end
 
if class_specifics.defaults ~= nil then
 
for key, value in pairs(class_specifics.defaults) do
 
c.item_classes[tpl_args.class_id].defaults[key] = value
 
end
 
end
 
end
 
end
 
 
function h.process_mods(tpl_args, frame)
 
-- If the item does not have its own implicit mods, fall back to the implicit mods on the base item.
 
local implicit_mods = tpl_args._defined_implicit_mods
 
if #implicit_mods == 0 then
 
implicit_mods = tpl_args._base_implicit_mods
 
end
 
for _, v in ipairs(implicit_mods) do
 
table.insert(tpl_args._mods, v)
 
end
 
if #tpl_args._mods > 0 then
 
 
local mods = {}
 
local mods = {}
 
local mod_ids = {}
 
local mod_ids = {}
Строка 303: Строка 674:
 
 
 
for _, key in ipairs(keys) do
 
for _, key in ipairs(keys) do
local req = math.floor(tonumber(data['mods.required_level']) * cfg.item_required_level_modifier_contribution)
+
local req = math.floor(tonumber(data['mods.required_level']) * 0.8)
 
if req > tpl_args[key] then
 
if req > tpl_args[key] then
 
tpl_args[key] = req
 
tpl_args[key] = req
Строка 341: Строка 712:
 
prefix = '_random'
 
prefix = '_random'
 
end
 
end
core.stats_update(tpl_args, id, value, mod_data.result['mods.id'], prefix .. '_stats')
+
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], prefix .. '_stats')
 
if mod_data.is_implicit then
 
if mod_data.is_implicit then
core.stats_update(tpl_args, id, value, mod_data.result['mods.id'], prefix .. '_implicit_stats')
+
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], prefix .. '_implicit_stats')
 
else
 
else
core.stats_update(tpl_args, id, value, mod_data.result['mods.id'], prefix .. '_explicit_stats')
+
h.stats_update(tpl_args, id, value, mod_data.result['mods.id'], prefix .. '_explicit_stats')
 
end
 
end
 
end
 
end
Строка 382: Строка 753:
 
 
 
if missing_sell_price then
 
if missing_sell_price then
tpl_args.sell_prices[i18n.tooltips.default_vendor_offer] = 1
+
tpl_args.sell_prices['Обрывок свитка'] = 1
 
end
 
end
 
 
Строка 399: Строка 770:
 
end
 
end
   
function h.process_base_item(tpl_args, frame)
+
function core.process_base_item(tpl_args, frame, args)
 
local where
 
local where
 
if tpl_args.base_item_id ~= nil then
 
if tpl_args.base_item_id ~= nil then
Строка 421: Строка 792:
 
local join = {}
 
local join = {}
 
 
for _, table_name in ipairs(c.item_classes[tpl_args.class_id].tables) do
+
for _, table_name in ipairs(core.item_classes[tpl_args.class_id].tables) do
 
if table_name ~= 'items' then
 
if table_name ~= 'items' then
 
join[#join+1] = string.format('items._pageID=%s._pageID', table_name)
 
join[#join+1] = string.format('items._pageID=%s._pageID', table_name)
Строка 441: Строка 812:
 
 
 
local result = m_cargo.query(
 
local result = m_cargo.query(
c.item_classes[tpl_args.class_id].tables,
+
core.item_classes[tpl_args.class_id].tables,
 
fields,
 
fields,
 
{
 
{
Строка 460: Строка 831:
 
 
 
tpl_args.base_item_data = result
 
tpl_args.base_item_data = result
h.process_arguments(tpl_args, frame, {'base_item', 'base_item_page', 'base_item_id'})
+
core.process_arguments(tpl_args, frame, {array={'base_item', 'base_item_page', 'base_item_id'}})
   
 
--Copy values..
 
--Copy values..
Строка 491: Строка 862:
 
end
 
end
   
function h.process_quest_rewards(tpl_args, frame)
+
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_id and core.item_classes[tpl_args.class_id].defaults ~= nil and core.item_classes[tpl_args.class_id].defaults[k] ~= nil then
  +
tpl_args[k] = core.item_classes[tpl_args.class_id].defaults[k]
  +
elseif data.default ~= nil then
  +
if type(data.default) == 'function' then
  +
tpl_args[k] = data.default(tpl_args, frame)
  +
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_id]
  +
if skip then
  +
skip = skip.skip_stat_lines
  +
end
  +
  +
local random_mods = {}
  +
  +
for _, modinfo in ipairs(tpl_args._mods) do
  +
if modinfo.is_implicit == args.is_implicit then
  +
if modinfo.is_random == true then
  +
if random_mods[modinfo.stat_text] then
  +
table.insert(random_mods[modinfo.stat_text], modinfo)
  +
else
  +
random_mods[modinfo.stat_text] = {modinfo}
  +
end
  +
else
  +
if modinfo.id == 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
  +
table.insert(lines, modinfo.text)
  +
else
  +
for _, line in ipairs(m_util.string.split(modinfo.result['mods.stat_text'] or '', '<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
  +
end
  +
  +
for stat_text, modinfo_list in pairs(random_mods) do
  +
local text = {}
  +
for _, modinfo in ipairs(modinfo_list) do
  +
table.insert(text, modinfo.result['mods.stat_text'])
  +
end
  +
  +
local tbl = mw.html.create('table')
  +
tbl
  +
:attr('class', 'random-modifier-stats mw-collapsed')
  +
:attr('style', 'text-align: left')
  +
:tag('tr')
  +
:tag('th')
  +
:attr('class', 'mw-customtoggle-31')
  +
:wikitext(stat_text)
  +
:done()
  +
:done()
  +
:tag('tr')
  +
:attr('class', 'mw-collapsible mw-collapsed')
  +
:attr('id', 'mw-customcollapsible-31')
  +
:tag('td')
  +
:wikitext(table.concat(text, '<hr style="width: 20%">'))
  +
:done()
  +
:done()
  +
table.insert(lines, tostring(tbl))
  +
end
  +
  +
if #lines == 0 then
  +
return
  +
else
  +
return table.concat(lines, '<br>')
  +
end
  +
end
  +
  +
function core.process_quest_rewards(tpl_args, frame)
 
local rid = 1
 
local rid = 1
 
local continue
 
local continue
Строка 589: Строка 1073:
 
end
 
end
   
-- Lazy loading for Module:Item2/cargo
 
function h.build_cargo_data(tpl_args, frame, item_classes)
 
if not f_build_cargo_data then
 
f_build_cargo_data = use_sandbox and require('Module:Item2/cargo/sandbox').build_cargo_data or require('Module:Item2/cargo').build_cargo_data
 
end
 
return f_build_cargo_data(tpl_args, frame, item_classes)
 
end
 
   
  +
function core.process_upgraded_from(tpl_args, frame)
-- Lazy loading for Module:Item2/upgrade
 
  +
local query_data = {
function h.process_upgraded_from(tpl_args, frame)
 
  +
id = {},
if not f_process_upgraded_from then
 
  +
name = {},
f_process_upgraded_from = use_sandbox and require('Module:Item2/upgrade/sandbox').process_upgraded_from or require('Module:Item2/upgrade').process_upgraded_from
 
  +
page = {},
  +
}
  +
local sets = {}
  +
  +
-- ------------------------------------------------------------------------
  +
-- Manual data
  +
-- ------------------------------------------------------------------------
  +
local setid = #sets + 1
  +
local set
  +
repeat
  +
local prefix = string.format('upgraded_from_set%s_', setid)
  +
local groupid = 1
  +
local group
  +
set = {
  +
groups = {},
  +
text = m_util.cast.text(tpl_args[prefix .. 'text']),
  +
automatic = false,
  +
}
  +
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 = m_util.cast.text(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 key, array in pairs(query_data) do
  +
local value = group['item_' .. key]
  +
if value then
  +
if array[value] then
  +
table.insert(array[value], {setid, groupid})
  +
else
  +
array[value] = {{setid, groupid}, }
  +
end
  +
end
  +
end
  +
set.groups[#set.groups+1] = group
  +
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
  +
-- ------------------------------------------------------------------------
  +
-- Automatic
  +
-- ------------------------------------------------------------------------
  +
  +
--
  +
-- maps
  +
--
  +
local automatic_index = #sets + 1
  +
-- TODO: 3.9.0 Unsure how this works yet, so disabled for now
  +
--[[if tpl_args.atlas_connections and tpl_args.rarity_id == "normal" then
  +
local results = m_cargo.query(
  +
{'items', 'maps'},
  +
{'items._pageName', 'items.name'},
  +
{
  +
join='items._pageID=maps._pageID',
  +
where=string.format('items.class_id = "Map" AND items.rarity_id = "normal" AND maps.tier < %s AND items._pageName IN ("%s")', tpl_args.map_tier, table.concat(tpl_args.atlas_connections, '", "')),
  +
}
  +
)
  +
for _, row in ipairs(results) do
  +
sets[#sets+1] = {
  +
text = i18n.misc.upgraded_from_map,
  +
groups = {
  +
{
  +
item_name = row['items.name'],
  +
item_page = row['items._pageName'],
  +
amount = 3,
  +
notes = nil,
  +
},
  +
},
  +
automatic = true,
  +
}
  +
end
  +
end]]
  +
  +
--
  +
-- oils
  +
--
  +
if tpl_args._flags.is_blight_item and tpl_args.blight_item_tier > 1 then
  +
local results = m_cargo.query(
  +
{'items', 'blight_items'},
  +
{'items._pageName', 'items.name'},
  +
{
  +
join='items._pageID=blight_items._pageID',
  +
where=string.format('blight_items.tier = %s', tpl_args.blight_item_tier - 1),
  +
}
  +
)
  +
for _, row in ipairs(results) do
  +
sets[#sets+1] = {
  +
text = nil,
  +
groups = {
  +
{
  +
item_name = row['items.name'],
  +
item_page = row['items._pageName'],
  +
amount = 3,
  +
notes = nil,
  +
},
  +
},
  +
automatic = true,
  +
}
  +
end
  +
end
  +
  +
  +
--
  +
-- essences
  +
--
  +
  +
-- exclude remnant of corruption via type
  +
if tpl_args.is_essence and tpl_args.essence_type > 0 then
  +
local results = m_cargo.query(
  +
{'items', 'essences'},
  +
{
  +
'items._pageName',
  +
'items.name',
  +
'items.metadata_id',
  +
'essences.category',
  +
'essences.type',
  +
},
  +
{
  +
join='items._pageID=essences._pageID',
  +
where=string.format([[
  +
(essences.category="%s" AND essences.level = %s)
  +
OR (essences.type = %s AND essences.level = %s)
  +
OR items.metadata_id = 'Metadata/Items/Currency/CurrencyCorruptMonolith'
  +
OR (%s = 6 AND essences.type = 5 AND essences.level >= 5)
  +
]],
  +
tpl_args.essence_category, tpl_args.essence_level - 1,
  +
tpl_args.essence_type - 1, tpl_args.essence_level,
  +
-- special case for corruption only essences
  +
tpl_args.essence_type
  +
),
  +
orderBy='essences.level ASC, essences.type ASC',
  +
}
  +
)
  +
  +
local remnant = results[1]
  +
if remnant['items.metadata_id'] ~= 'Metadata/Items/Currency/CurrencyCorruptMonolith' then
  +
error(string.format('Something went seriously wrong here. Got results: %s', mw.dumpObject(results)))
  +
end
  +
for i=2, #results do
  +
local row = results[i]
  +
if row['essences.category'] == tpl_args.essence_category then
  +
-- 3 to 1 recipe
  +
sets[#sets+1] = {
  +
automatic = true,
  +
text = nil,
  +
groups = {
  +
{
  +
item_id = row['items.metadata_id'],
  +
item_page = row['items._pageName'],
  +
item_name = row['items.name'],
  +
amount = 3,
  +
},
  +
},
  +
}
  +
-- corruption +1
  +
sets[#sets+1] = {
  +
automatic = true,
  +
text = i18n.upgraded_from_tooltips.essence_plus_one_level,
  +
groups = {
  +
{
  +
item_id = row['items.metadata_id'],
  +
item_page = row['items._pageName'],
  +
item_name = row['items.name'],
  +
amount = 1,
  +
},
  +
{
  +
item_id = remnant['items.metadata_id'],
  +
item_page = remnant['items._pageName'],
  +
item_name = remnant['items.name'],
  +
amount = 1,
  +
},
  +
},
  +
}
  +
elseif tonumber(row['essences.type']) == tpl_args.essence_type - 1 then
  +
-- corruption type change
  +
sets[#sets+1] = {
  +
automatic = true,
  +
text = i18n.upgraded_from_tooltips.essence_type_change,
  +
groups = {
  +
{
  +
item_id = row['items.metadata_id'],
  +
item_page = row['items._pageName'],
  +
item_name = row['items.name'],
  +
amount = 1,
  +
},
  +
{
  +
item_id = remnant['items.metadata_id'],
  +
item_page = remnant['items._pageName'],
  +
item_name = remnant['items.name'],
  +
amount = 1,
  +
},
  +
},
  +
}
  +
end
  +
end
  +
end
  +
  +
-- data based on mapping
  +
if tpl_args.drop_enabled and not tpl_args.upgraded_from_disabled then
  +
for _, data in ipairs(core.automatic_upgraded_from) do
  +
data.defaults = data.defaults or {}
  +
local continue = true
  +
for key, value in pairs(core.automatic_upgraded_from_defaults) do
  +
local func
  +
local v = data.defaults[key]
  +
if v == false then
  +
-- check is disabled specifically, continue
  +
elseif v == nil then
  +
func = value
  +
elseif type(v) == 'function' then
  +
func = v
  +
else
  +
error(string.format('Invalid value for defaults at data %s', mw.dumpObject(data)))
  +
end
  +
if func then
  +
continue = func(tpl_args, frame) and continue
  +
if not continue then
  +
break
  +
end
  +
end
  +
end
  +
for _, condition in ipairs(data.condition) do
  +
continue = condition(tpl_args, frame) and continue
  +
if not continue then
  +
break
  +
end
  +
end
  +
  +
if continue then
  +
sets[#sets+1] = {
  +
automatic = true,
  +
text = data.text(tpl_args, frame),
  +
groups = data.groups,
  +
}
  +
for groupid, row in ipairs(data.groups) do
  +
if query_data['id'][row.item_id] then
  +
table.insert(query_data['id'][row.item_id], {#sets, groupid})
  +
else
  +
query_data['id'][row.item_id] = {{#sets, groupid}, }
  +
end
  +
end
  +
end
  +
end
  +
end
  +
  +
if #sets == 0 then
  +
return
  +
end
  +
--
  +
-- Fetch item data in a single query to sacrifice database load with a lot of upgraded_from references
  +
--
  +
local query_data_array = {
  +
id = {},
  +
name = {},
  +
page = {},
  +
}
  +
local query_fields = {
  +
id = 'items.metadata_id',
  +
page = 'items._pageName',
  +
name = 'items.name',
  +
}
  +
local where = {}
  +
local expected_count = 0
  +
for key, thing_array in pairs(query_data) do
  +
for thing, _ in pairs(thing_array) do
  +
table.insert(query_data_array[key], thing)
  +
end
  +
if #query_data_array[key] > 0 then
  +
expected_count = expected_count + #query_data_array[key]
  +
local q_data = table.concat(query_data_array[key], '", "')
  +
table.insert(where, string.format('%s IN ("%s")', query_fields[key], q_data))
  +
end
  +
end
  +
local results = m_cargo.query(
  +
{'items'},
  +
{'items._pageName', 'items.name', 'items.metadata_id'},
  +
{
  +
where=table.concat(where, ' OR '),
  +
}
  +
)
  +
  +
for _, row in ipairs(results) do
  +
for key, thing_array in pairs(query_data) do
  +
local set_groups = thing_array[row[query_fields[key]]]
  +
if set_groups then
  +
for _, set_group in ipairs(set_groups) do
  +
local entry = sets[set_group[1]].groups[set_group[2]]
  +
for entry_key, data_key in pairs(query_fields) do
  +
-- metadata_id may be nil, since we don't know them for unique items
  +
if row[data_key] then
  +
entry['item_' .. entry_key] = row[data_key]
  +
end
  +
end
  +
end
  +
-- set this to nil for error checking in later step
  +
thing_array[row[query_fields[key]]] = nil
  +
end
  +
end
  +
end
  +
  +
-- sbow the broken references if needed
  +
if #results ~= expected_count then
  +
-- query data was pruned of existing keys earlier, so only broken keys remain
  +
for key, array in pairs(query_data) do
  +
for thing, set_groups in pairs(array) do
  +
for _, set_group in ipairs(set_groups) do
  +
tpl_args._flags.broken_upgraded_from_reference = true
  +
tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.upgraded_from_broken_reference, string.format('upgraded_from_set%s_group%s_item_%s', set_group[1], set_group[2], key), thing)
  +
end
  +
end
  +
end
  +
end
  +
  +
--
  +
-- Check for duplicates
  +
--
  +
local delete_sets = {}
  +
for i=automatic_index, #sets do
  +
for j=1, automatic_index-1 do
  +
if #sets[i].groups == #sets[j].groups then
  +
local match = true
  +
for row_id, row in ipairs(sets[i].groups) do
  +
-- Only the fields from the database query are matched since we can be sure they're correct. Other fields may be subject to user error.
  +
for _, key in ipairs({'item_id', 'item_name', 'item_page'}) do
  +
match = match and (row[key] == sets[j].groups[row_id][key])
  +
end
  +
end
  +
if match then
  +
tpl_args._flags.duplicate_upgraded_from_reference = true
  +
tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.upgraded_from_duplicate, j)
  +
delete_sets[#delete_sets+1] = j
  +
end
  +
end
  +
end
  +
end
  +
  +
for offset, index in ipairs(delete_sets) do
  +
table.remove(sets, index-(offset-1))
  +
end
  +
--
  +
-- Set data
  +
--
  +
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,
  +
automatic = set.automatic,
  +
}
  +
  +
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.item_page,
  +
amount = group.amount,
  +
notes = group.notes,
  +
}
  +
end
 
end
 
end
return f_process_upgraded_from(tpl_args, frame)
 
 
end
 
end
   
 
--
 
--
  +
-- function factory
-- Display
 
 
--
 
--
  +
core.factory = {}
   
  +
function core.factory.cast_text(k, args)
function h.strip_random_stats(tpl_args, frame, stat_text, container_type)
 
  +
args = args or {}
if tpl_args._flags.random_mods and container_type == 'inline' then
 
  +
return function (tpl_args, frame)
repeat
 
  +
tpl_args[args.key_out or k] = m_util.cast.text(tpl_args[k])
local text = string.match(stat_text, '<th class="mw%-customtoggle%-31">(.+)</th>')
 
if text ~= nil then
 
stat_text = string.gsub(stat_text, '<table class="random%-modifier%-stats.+</table>', text, 1)
 
end
 
until text == nil
 
 
end
 
end
return stat_text
 
 
end
 
end
   
  +
function core.factory.display_value(args)
function h.add_to_container_from_map(tpl_args, frame, container, mapping, container_type)
 
  +
-- args:
local statcont = mw.html.create('span')
 
  +
-- type: Type of the keys (nil = regular, gem = skill gems, stat = stats)
statcont
 
  +
-- options<Array>:
:attr('class', 'item-stats')
 
:done()
+
-- key: key to use
  +
-- allow_zero: allow zero values
local count = 0 -- Number of groups in container
 
  +
-- hide_default: hide the value if this is set
for _, group in ipairs(mapping) do
 
  +
-- hide_default_key: key to use if it isn't equal to the key parameter
local lines = {}
 
if group.func == nil then
+
-- -- from m_util.html.format_value --
for _, line in ipairs(group) do
+
-- fmt: formatter to use for the value instead of valfmt
local show = true
+
-- fmt_range: formatter to use for the range values. Default: (%s to %s)
  +
-- insert: insert results into this object
if container_type == 'inline' and line.inline == false then -- TODO: This is not used currently. Need to address what is hidden in inline view.
 
show = false
+
-- func: Function to adjust the value with before output
  +
-- color: colour code for m_util.html.poe_color, overrides mod colour
elseif line.show == false then
 
show = false
+
-- no_color: set to true to ingore colour entirely
  +
-- truncate: set to true to truncate the line
elseif type(line.show) == 'function' then
 
  +
show = line.show(tpl_args, frame, container_type)
 
  +
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_id] 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
if show then
+
end
  +
elseif args.type == 'stat' then
lines[#lines+1] = line.func(tpl_args, frame, container_type)
 
  +
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
 
end
 
end
 
else
 
else
lines = group.func(tpl_args, frame, container_type)
+
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
 
end
if #lines > 0 then
+
count = count + 1
+
local final_values = {}
local heading = ''
+
for i, data in ipairs(temp_values) do
if group.heading == nil then
+
local opt = args.options[data.index]
elseif type(group.heading) == 'function' then
+
local insert = false
heading = group.heading()
+
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
 
else
heading = string.format('<em class="header">%s</em><br>', group.heading)
+
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
statcont
+
end
:tag('span')
+
  +
-- all zeros = dont display and return early
:attr('class', 'group ' .. (group.class or ''))
 
  +
if #final_values == 0 then
:wikitext(heading .. table.concat(lines, '<br>'))
 
:done()
+
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
  +
if options.truncate then
  +
options.class = 'u-truncate-line'
  +
end
  +
  +
out[#out+1] = m_util.html.format_value(tpl_args, frame, value, options)
  +
end
  +
  +
if args.inline then
  +
return m_util.html.poe_color(args.color or 'default', string.format(args.inline, unpack(out)))
  +
else
  +
return table.concat(out, '')
 
end
 
end
 
end
 
end
  +
end
   
  +
function core.factory.display_value_only(key)
-- Add groups to container
 
  +
return function(tpl_args, frame)
if count > 0 then
 
container:node(statcont)
+
return tpl_args[key]
 
end
 
end
 
end
 
end
   
  +
function core.factory.descriptor_value(args)
function h.make_main_container(tpl_args, frame, container_type)
 
  +
-- 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 .. '&ndash;' .. 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
  +
  +
--
  +
-- Display
  +
--
  +
core.display = {}
  +
  +
function core.display.make_main_container(tpl_args, frame, container_type)
 
local container = mw.html.create('span')
 
local container = mw.html.create('span')
 
:attr('class', 'item-box -' .. tpl_args.frame_type)
 
:attr('class', 'item-box -' .. tpl_args.frame_type)
Строка 753: Строка 1749:
 
:done()
 
:done()
 
 
h.add_to_container_from_map(tpl_args, frame, container, c.item_infobox_groups, container_type)
+
core.display.add_to_container_from_map(tpl_args, frame, container, core.item_display_groups, container_type)
 
end
 
end
 
 
Строка 763: Строка 1759:
 
end
 
end
   
  +
function core.display.add_to_container_from_map(tpl_args, frame, container, mapping, container_type)
--
 
  +
local grpcont
-- Factory
 
  +
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
  +
if container_type == 'inline' and disp.inline == false then
  +
valid = false
  +
else
  +
-- 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, container_type)
  +
end
  +
end
  +
if valid then
  +
grpcont[#grpcont+1] = disp.func(tpl_args, frame, container_type)
  +
end
  +
end
  +
else
  +
grpcont = group.func(tpl_args, frame, container_type)
  +
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
   
  +
function core.display.strip_random_stats(tpl_args, frame, stat_text, container_type)
h.factory = {}
 
  +
if tpl_args._flags.random_mods and container_type == 'inline' then
  +
repeat
  +
local text = string.match(stat_text, '<th class="mw%-customtoggle%-31">(.+)</th>')
  +
if text ~= nil then
  +
stat_text = string.gsub(stat_text, '<table class="random%-modifier%-stats.+</table>', text, 1)
  +
end
  +
until text == nil
  +
end
  +
return stat_text
  +
end
   
  +
--
function h.factory.args_present(...)
 
  +
-- argument mapping
local args = {...}
 
  +
--
return function (tpl_args)
 
  +
-- format:
for _, k in ipairs(args) do
 
  +
-- tpl_args key = {
if tpl_args[k] == nil then
 
  +
-- no_copy = true or nil -- When loading an base item, dont copy this key
return false
 
  +
-- 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,
  +
},
  +
html_extra = {
  +
no_copy = true,
  +
field = 'html_extra',
  +
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, {is_implicit=true})
  +
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, {is_implicit=false})
  +
  +
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 = i18n.tooltips.corrupted
  +
else
  +
tpl_args.explicit_stat_text = (tpl_args.explicit_stat_text or '') .. '<br>' .. 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,
  +
},
  +
class = {
  +
no_copy = true,
  +
field = 'class',
  +
type = 'String',
  +
func = function (tpl_args, frame)
  +
tpl_args.class = m_game.constants.item.classes[tpl_args.class_id]['long_upper']
  +
-- Avoids errors with empty item class names later on
  +
if tpl_args.class == '' then
  +
tpl_args.class = nil
  +
end
  +
end,
  +
},
  +
-- processed in core.build_item_classes
  +
class_id = {
  +
no_copy = true,
  +
field = 'class_id',
  +
type = 'String',
  +
func = function (tpl_args, frame)
  +
if m_game.constants.item.classes[tpl_args.class_id] == nil then
  +
error(string.format(i18n.errors.invalid_class_id, tostring(tpl_args.class_id)))
 
end
 
end
 
end
 
end
return true
+
},
  +
-- generic
  +
rarity_id = {
  +
no_copy = true,
  +
field = 'rarity_id',
  +
type = 'String',
  +
func = function (tpl_args, frame)
  +
if m_game.constants.rarities[tpl_args.rarity_id] == nil then
  +
error(string.format(i18n.errors.invalid_rarity_id, tostring(tpl_args.rarity_id)))
  +
end
  +
end
  +
},
  +
rarity = {
  +
no_copy = true,
  +
field = 'rarity',
  +
type = 'String',
  +
func = function(tpl_args, frame)
  +
tpl_args.rarity = m_game.constants.rarities[tpl_args.rarity_id]['long_upper']
  +
end
  +
},
  +
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_rarities_ids = {
  +
no_copy = true,
  +
field = 'drop_rarity_ids',
  +
type = 'List (,) of Text',
  +
func = function(tpl_args, frame)
  +
tpl_args.drop_rarities_ids = nil
  +
if true then return end
  +
-- Drop rarities only matter for base items.
  +
if tpl_args.rarity_id ~= 'normal' then
  +
return
  +
end
  +
  +
if tpl_args.drop_rarities_ids == nil then
  +
tpl_args.drop_rarities_ids = {}
  +
return
  +
end
  +
  +
tpl_args.drop_rarities_ids = m_util.string.split(tpl_args.drop_rarities_ids, ',%s*')
  +
for _, rarity_id in ipairs(tpl_args.drop_rarities_ids) do
  +
if m_game.constants.rarities[rarity_id] == nil then
  +
error(string.format(i18n.errors.invalid_rarity_id, tostring(rarity_id)))
  +
end
  +
end
  +
end,
  +
},
  +
drop_rarities = {
  +
no_copy = true,
  +
field = nil,
  +
type = 'List (,) of Text',
  +
func = function(tpl_args, frame)
  +
tpl_args.drop_rarities = nil
  +
if true then return end
  +
-- Drop rarities only matter for base items.
  +
if tpl_args.rarity_id ~= 'normal' then
  +
return
  +
end
  +
  +
local rarities = {}
  +
for _, rarity_id in ipairs(tpl_args.drop_rarities_ids) do
  +
rarities[#rarities+1] = m_game.constants.rarities[rarity_id].full
  +
end
  +
tpl_args.drop_rarities = rarities
  +
end,
  +
},
  +
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
  +
tpl_args.drop_areas = m_util.string.split(tpl_args.drop_areas, ',%s*')
  +
tpl_args.drop_areas_data = m_cargo.array_query{
  +
tables={'areas'},
  +
fields={'areas._pageName', 'areas.id', 'areas.name', 'areas.main_page'},
  +
id_field='areas.id',
  +
id_array=tpl_args.drop_areas,
  +
query={limit=5000},
  +
}
  +
end
  +
  +
-- find areas based on item tags for atlas bases
  +
local query_data
  +
for _, tag in ipairs(tpl_args.tags or {}) do
  +
query_data = nil
  +
if string.match(tag, '[%w_]*atlas[%w_]*') ~= nil and tag ~= 'atlas_base_type' then
  +
query_data = m_cargo.query(
  +
{'atlas_maps', 'maps', 'areas', 'atlas_base_item_types'},
  +
{'areas._pageName', 'areas.id', 'areas.name', 'areas.main_page'},
  +
{
  +
join='atlas_maps._pageID=maps._pageID, maps.area_id=areas.id, atlas_maps.region_id=atlas_base_item_types.region_id',
  +
where=string.format([[
  +
atlas_base_item_types.tag = "%s"
  +
AND atlas_base_item_types.weight > 0
  +
AND (
  +
atlas_maps.map_tier0 >= tier_min AND atlas_maps.map_tier0 <= atlas_base_item_types.tier_max
  +
OR atlas_maps.map_tier1 >= tier_min AND atlas_maps.map_tier1 <= atlas_base_item_types.tier_max
  +
OR atlas_maps.map_tier2 >= tier_min AND atlas_maps.map_tier2 <= atlas_base_item_types.tier_max
  +
OR atlas_maps.map_tier3 >= tier_min AND atlas_maps.map_tier3 <= atlas_base_item_types.tier_max
  +
OR atlas_maps.map_tier4 >= tier_min AND atlas_maps.map_tier4 <= atlas_base_item_types.tier_max
  +
)]],
  +
tag),
  +
groupBy='areas.id',
  +
}
  +
)
  +
end
  +
  +
if query_data ~= nil then
  +
-- in case no manual drop areas have been set
  +
if tpl_args.drop_areas == nil then
  +
tpl_args.drop_areas = {}
  +
tpl_args.drop_areas_data = {}
  +
end
  +
local drop_areas_assoc = {}
  +
for _, id in ipairs(tpl_args.drop_areas) do
  +
drop_areas_assoc[id] = true
  +
end
  +
  +
local duplicates = {}
  +
  +
for _, row in ipairs(query_data) do
  +
if drop_areas_assoc[row['areas.id']] == nil then
  +
tpl_args.drop_areas[#tpl_args.drop_areas+1] = row['areas.id']
  +
tpl_args.drop_areas_data[#tpl_args.drop_areas_data+1] = row
  +
else
  +
duplicates[#duplicates+1] = row['areas.id']
  +
end
  +
end
  +
  +
if #duplicates > 0 then
  +
tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.duplicate_area_id_from_query, table.concat(duplicates, ', '))
  +
tpl_args._flags.duplicate_query_area_ids = true
  +
end
  +
end
  +
end
  +
end,
  +
},
  +
drop_monsters = {
  +
no_copy = true,
  +
field = 'drop_monsters',
  +
type = 'List (,) of Text',
  +
func = function (tpl_args, frame)
  +
if tpl_args.drop_monsters ~= nil then
  +
tpl_args.drop_monsters = m_util.string.split(tpl_args.drop_monsters, ',%s*')
  +
end
  +
end,
  +
},
  +
drop_text = {
  +
no_copy = true,
  +
field = 'drop_text',
  +
type = 'Text',
  +
func = core.factory.cast_text('drop_text'),
  +
},
  +
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_id == 'DivinationCard' 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 (tpl_args, frame) 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 = {
  +
debug_ignore_nil = true,
  +
field = 'help_text',
  +
type = 'Text',
  +
func = core.factory.cast_text('help_text'),
  +
},
  +
flavour_text = {
  +
no_copy = true,
  +
field = 'flavour_text',
  +
type = 'Text',
  +
func = core.factory.cast_text('flavour_text'),
  +
},
  +
flavour_text_id = {
  +
no_copy = true,
  +
field = 'flavour_text_id',
  +
type = 'String',
  +
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',
  +
--type = 'String(unique; size=200)',
  +
func = function(tpl_args, frame)
  +
if tpl_args.metadata_id == nil then
  +
return
  +
end
  +
local results = m_cargo.query(
  +
{'items'},
  +
{'items._pageName'},
  +
{
  +
where=string.format('items.metadata_id="%s" AND items._pageName != "%s"', tpl_args.metadata_id, mw.title.getCurrentTitle().fullText)
  +
}
  +
)
  +
if #results > 0 and tpl_args.debug_id == nil and not tpl_args.debug then
  +
error(string.format(i18n.errors.duplicate_metadata, tpl_args.metadata_id, results[1]['items._pageName']))
  +
end
  +
end,
  +
},
  +
influences = {
  +
no_copy = true,
  +
field = 'influences',
  +
type = 'List (,) of String',
  +
func = m_util.cast.factory.assoc_table('influences', {
  +
tbl = m_game.constants.influences,
  +
errmsg = i18n.errors.invalid_influence,
  +
}),
  +
},
  +
is_fractured = {
  +
no_copy = true,
  +
field = 'is_fractured',
  +
type = 'Boolean',
  +
func = m_util.cast.factory.boolean('is_fractured'),
  +
default = false,
  +
},
  +
is_synthesised = {
  +
no_copy = true,
  +
field = 'is_synthesised',
  +
type = 'Boolean',
  +
func = m_util.cast.factory.boolean('is_synthesised'),
  +
default = false,
  +
},
  +
is_veiled = {
  +
no_copy = true,
  +
field = 'is_veiled',
  +
type = 'Boolean',
  +
func = m_util.cast.factory.boolean('is_veiled'),
  +
default = false,
  +
},
  +
is_replica = {
  +
no_copy = true,
  +
field = 'is_replica',
  +
type = 'Boolean',
  +
func = function(tpl_args, frame)
  +
m_util.cast.factory.boolean('is_replica')(tpl_args, frame)
  +
if tpl_args.is_replica == true and tpl_args.rarity_id ~= 'unique' then
  +
error(string.format(i18n.errors.non_unique_flag, 'is_replica'))
  +
end
  +
end,
  +
default = false,
  +
},
  +
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_id ~= 'unique' then
  +
error(string.format(i18n.errors.non_unique_flag, 'is_relic'))
  +
end
  +
end,
  +
default = false,
  +
},
  +
is_fated = {
  +
no_copy = true,
  +
field = 'is_fated',
  +
type = 'Boolean',
  +
func = function(tpl_args, frame)
  +
m_util.cast.factory.boolean('is_fated')(tpl_args, frame)
  +
if tpl_args.is_fated == true and tpl_args.rarity_id ~= 'unique' then
  +
error(string.format(i18n.errors.non_unique_flag, 'is_fated'))
  +
end
  +
end,
  +
default = false,
  +
},
  +
is_prophecy = {
  +
no_copy = true,
  +
field = nil,
  +
type = nil,
  +
func = function(tpl_args, frame)
  +
tpl_args._flags.is_prophecy = (tpl_args.metadata_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy' or tpl_args.base_item == 'Пророчество' or tpl_args.base_item_page == 'Пророчество' or tpl_args.base_item_id == 'Metadata/Items/Currency/CurrencyItemisedProphecy')
  +
end
  +
},
  +
is_blight_item = {
  +
no_copy = true,
  +
field = nil,
  +
type = nil,
  +
func = function(tpl_args, frame)
  +
tpl_args._flags.is_blight_item = (tpl_args.blight_item_tier ~= nil)
  +
end
  +
},
  +
is_drop_restricted = {
  +
no_copy = true,
  +
field = 'is_drop_restricted',
  +
type = 'Boolean',
  +
func = m_util.cast.factory.boolean('is_drop_restricted'),
  +
default = function(tpl_args, frame)
  +
-- Generally items that are obtained only from specific monsters can't be obtained via cards or other means unless specifically specified
  +
for _, var in ipairs({'is_talisman', 'is_fated', 'is_relic', 'drop_monsters'}) do
  +
if tpl_args[var] then
  +
return true
  +
end
  +
end
  +
for _, flag in ipairs({'is_prophecy', 'is_blight_item'}) do
  +
if tpl_args._flags[flag] then
  +
return true
  +
end
  +
end
  +
return false
  +
end,
  +
},
  +
purchase_costs = {
  +
func = function(tpl_args, frame)
  +
local purchase_costs = {}
  +
for _, rarity_id in ipairs(m_game.constants.rarity_order) do
  +
local rtbl = {}
  +
local prefix = string.format('purchase_cost_%s', rarity_id)
  +
local i = 1
  +
while i ~= -1 do
  +
local iprefix = prefix .. i
  +
local values = {
  +
name = tpl_args[iprefix .. '_name'],
  +
amount = tonumber(tpl_args[iprefix .. '_amount']),
  +
rarity = rarity_id,
  +
}
  +
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_id] = rtbl
  +
end
  +
  +
tpl_args.purchase_costs = purchase_costs
  +
end,
  +
func_fetch = function(tpl_args, frame)
  +
if tpl_args.rarity_id ~= 'unique' then
  +
return
  +
end
  +
  +
local results = m_cargo.query(
  +
{'items' ,'item_purchase_costs'},
  +
{'item_purchase_costs.amount', 'item_purchase_costs.name', '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),
  +
}
  +
)
  +
  +
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,
  +
},
  +
sell_prices_override = {
  +
no_copy = true,
  +
func = function(tpl_args, frame)
  +
-- these variables are also used by mods when setting automatic sell prices
  +
tpl_args.sell_prices = {}
  +
tpl_args.sell_price_order = {}
  +
  +
  +
local name
  +
local amount
  +
local i = 0
  +
repeat
  +
i = i + 1
  +
name = tpl_args[string.format('sell_price%s_name', i)]
  +
amount = tpl_args[string.format('sell_price%s_amount', i)]
  +
  +
if name ~= nil and amount ~= nil then
  +
tpl_args.sell_price_order[#tpl_args.sell_price_order+1] = name
  +
tpl_args.sell_prices[name] = amount
  +
tpl_args._subobjects[#tpl_args._subobjects+1] = {
  +
_table = 'item_sell_prices',
  +
amount = amount,
  +
name = name,
  +
}
  +
end
  +
until name == nil or amount == nil
  +
  +
-- if sell prices are set, the override is active
  +
for _, _ in pairs(tpl_args.sell_prices) do
  +
tpl_args._flags.sell_prices_override = true
  +
break
  +
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_id ~= 'unique' then
  +
quality = 0
  +
elseif core.class_groups.weapons.keys[tpl_args.class_id] or core.class_groups.armor.keys[tpl_args.class_id] 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_id == 'UtilityFlask' or tpl_args.class_id == 'UtilityFlaskCritical' then
  +
h.stats_update(tpl_args, 'quality_flask_duration', stat, nil, '_stats')
  +
-- quality is added to quantity for maps
  +
elseif tpl_args.class_id == 'Map' 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 = 'Float',
  +
func = m_util.cast.factory.number('flask_duration'),
  +
},
  +
buff_id = {
  +
field = 'id',
  +
type = 'String',
  +
func = nil,
  +
},
  +
buff_values = {
  +
field = 'buff_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
  +
  +
-- needed so the values copyied from unique item base isn't overriden
  +
if #values >= 1 then
  +
tpl_args.buff_values = values
  +
end
  +
end,
  +
func_copy = function(tpl_args, frame)
  +
tpl_args.buff_values = m_util.string.split(tpl_args.buff_values, ',%s*')
  +
end,
  +
default = function (tpl_args, frame) return {} 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 = 'Float',
  +
func = m_util.cast.factory.number('critical_strike_chance'),
  +
},
  +
attack_speed = {
  +
field = 'attack_speed',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('attack_speed'),
  +
},
  +
weapon_range = {
  +
field = 'weapon_range',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('weapon_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,
  +
},
  +
-- This is the inherent penality from the armour piece if any
  +
movement_speed = {
  +
field = 'movement_speed',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('movement_speed'),
  +
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 = core.factory.cast_text('gem_description'),
  +
},
  +
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.attribute_order) do
  +
local val = tpl_args[attr .. '_percent']
  +
if val and val >= 60 then
  +
tpl_args['primary_attribute'] = attr
  +
return
  +
end
  +
end
  +
tpl_args['primary_attribute'] = 'none'
  +
end,
  +
},
  +
gem_tags = {
  +
field = 'gem_tags',
  +
type = 'List (,) of String',
  +
-- TODO: default rework
  +
func = function(tpl_args, frame)
  +
if tpl_args.gem_tags then
  +
tpl_args.gem_tags = m_util.string.split(tpl_args.gem_tags, ',%s*')
  +
end
  +
end,
  +
default = function (tpl_args, frame) return {} 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('map_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 = {
  +
field = 'unique_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,
  +
},
  +
map_series = {
  +
field = 'series',
  +
type = 'String',
  +
func = function(tpl_args, frame)
  +
if tpl_args.rarity == 'normal' and tpl_args.map_series == nil then
  +
error(string.format(i18n.errors.generic_required_parameter, 'map_series'))
  +
end
  +
end,
  +
},
  +
-- atlas info is only for the current map series
  +
atlas_x = {
  +
field = 'x',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_x'),
  +
},
  +
atlas_y = {
  +
field = 'y',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_y'),
  +
},
  +
atlas_region_id = {
  +
field = 'region_id',
  +
type = 'String',
  +
func = nil,
  +
},
  +
atlas_region_minimum = {
  +
field = 'region_minimum',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('atlas_region_minimum'),
  +
},
  +
atlas_x0 = {
  +
field = 'x0',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_x0'),
  +
},
  +
atlas_x1 = {
  +
field = 'x1',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_x1'),
  +
},
  +
atlas_x2 = {
  +
field = 'x2',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_x2'),
  +
},
  +
atlas_x3 = {
  +
field = 'x3',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_x3'),
  +
},
  +
atlas_x4 = {
  +
field = 'x4',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_x4'),
  +
},
  +
atlas_y0 = {
  +
field = 'y0',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_0'),
  +
},
  +
atlas_y1 = {
  +
field = 'y1',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_y1'),
  +
},
  +
atlas_y2 = {
  +
field = 'y2',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_y2'),
  +
},
  +
atlas_y3 = {
  +
field = 'y3',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_y3'),
  +
},
  +
atlas_y4 = {
  +
field = 'y4',
  +
type = 'Float',
  +
func = m_util.cast.factory.number('atlas_y4'),
  +
},
  +
atlas_map_tier0 = {
  +
field = 'map_tier0',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('atlas_map_tier0'),
  +
},
  +
atlas_map_tier1 = {
  +
field = 'map_tier1',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('atlas_map_tier1'),
  +
},
  +
atlas_map_tier2 = {
  +
field = 'map_tier2',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('atlas_map_tier2'),
  +
},
  +
atlas_map_tier3 = {
  +
field = 'map_tier3',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('atlas_map_tier3'),
  +
},
  +
atlas_map_tier4 = {
  +
field = 'map_tier4',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('atlas_map_tier4'),
  +
},
  +
atlas_connections = {
  +
field = nil,
  +
type = nil,
  +
func = function(tpl_args, frame)
  +
tpl_args.atlas_connections = {}
  +
  +
local cont = true
  +
local i = 1
  +
while cont do
  +
local prefix = string.format('atlas_connection%s_', i)
  +
local regions = tpl_args[prefix .. 'tier']
  +
local data = {
  +
_table = 'atlas_connections',
  +
map1 = string.format('%s (%s)', tpl_args.name, tpl_args.map_series or ''),
  +
map2 = tpl_args[prefix .. 'target'],
  +
}
  +
  +
if regions and data.map2 then
  +
regions = m_util.string.split(regions, ',%s*')
  +
if #regions ~= 5 then
  +
error(string.format(i18n.errors.invalid_region_upgrade_count, i, #regions))
  +
end
  +
for index, value in ipairs(regions) do
  +
data['region' .. (index - 1)] = m_util.cast.boolean(value)
  +
end
  +
  +
tpl_args.atlas_connections[data.map2] = data
  +
table.insert(tpl_args._subobjects, data)
  +
else
  +
cont = false
  +
if i == 1 then
  +
tpl_args.atlas_connections = nil
  +
end
  +
end
  +
  +
i = i + 1
  +
end
  +
end,
  +
default = nil,
  +
},
  +
--
  +
-- 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 = core.factory.cast_text('description'),
  +
},
  +
cosmetic_type = {
  +
field = 'cosmetic_type',
  +
type = 'String',
  +
func = core.factory.cast_text('cosmetic_type'),
  +
},
  +
-- 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'),
  +
},
  +
essence_type = {
  +
field = 'type',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('essence_type'),
  +
},
  +
essence_category = {
  +
field = 'category',
  +
type = 'String',
  +
func = nil,
  +
},
  +
-- blight crafting items (i.e. oils)
  +
blight_item_tier = {
  +
field = 'tier',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('blight_item_tier'),
  +
},
  +
-- harvest seeds
  +
seed_type_id = {
  +
field = 'type_id',
  +
type = 'String',
  +
},
  +
seed_type = {
  +
field = 'type',
  +
type = 'String',
  +
func = function (tpl_args, frame)
  +
if tpl_args.seed_type_id ~= 'none' or tpl_args.seed_type_id ~= nil then
  +
tpl_args.seed_type = m_game.seed_types[tpl_args.seed_type_id]
  +
end
  +
end
  +
},
  +
seed_type_html = {
  +
field = nil,
  +
type = nil,
  +
func = function (tpl_args, frame)
  +
if tpl_args.seed_type ~= nil then
  +
tpl_args.seed_type_html = m_util.html.poe_color(tpl_args.seed_type_id, tpl_args.seed_type)
  +
end
  +
end
  +
},
  +
seed_effect = {
  +
field = 'effect',
  +
type = 'Text',
  +
},
  +
seed_tier = {
  +
field = 'tier',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('seed_tier'),
  +
},
  +
seed_growth_cycles = {
  +
field = 'growth_cycles',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('seed_growth_cycles'),
  +
},
  +
seed_required_nearby_seed_tier = {
  +
field = 'required_nearby_seed_tier',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('seed_required_nearby_seed_tier'),
  +
},
  +
seed_required_nearby_seed_amount = {
  +
field = 'required_nearby_seed_amount',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('seed_required_nearby_seed_amount'),
  +
},
  +
seed_consumed_wild_lifeforce_percentage = {
  +
field = 'consumed_wild_lifeforce_percentage',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('seed_consumed_wild_lifeforce_percentage'),
  +
default = 0,
  +
},
  +
seed_consumed_vivid_lifeforce_percentage = {
  +
field = 'consumed_vivid_lifeforce_percentage',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('seed_consumed_vivid_lifeforce_percentage'),
  +
default = 0,
  +
},
  +
seed_consumed_primal_lifeforce_percentage = {
  +
field = 'consumed_primal_lifeforce_percentage',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('seed_consumed_primal_lifeforce_percentage'),
  +
default = 0,
  +
},
  +
seed_granted_craft_option_ids = {
  +
field = 'granted_craft_option_ids',
  +
type = 'List (,) of String',
  +
func = m_util.cast.factory.number('seed_grandted_craft_option_ids'),
  +
default = 0,
  +
},
  +
--
  +
-- harvest planet boosters
  +
--
  +
plant_booster_radius = {
  +
field = 'radius',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('plant_booster_radius'),
  +
},
  +
plant_booster_lifeforce = {
  +
field = 'lifeforce',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('plant_booster_lifeforce'),
  +
},
  +
plant_booster_additional_crafting_options = {
  +
field = 'additional_crafting_options',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('plant_booster_additional_crafting_options'),
  +
},
  +
plant_booster_extra_chances = {
  +
field = 'extra_chances',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('plant_booster_extra_chances'),
  +
},
  +
--
  +
-- Heist properties
  +
--
  +
heist_required_job_id = {
  +
field = 'required_job_id',
  +
type = 'String',
  +
func = core.factory.cast_text('heist_required_job_id'),
  +
},
  +
heist_required_job_level = {
  +
field = 'required_job_level',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('heist_required_job_level'),
  +
},
  +
heist_data = {
  +
func = function (tpl_args, frame)
  +
if tpl_args.heist_required_job_level then
  +
if tpl_args.heist_required_job_id then
  +
local results = m_cargo.query(
  +
{'heist_jobs', 'heist_npcs', 'heist_npc_skills'},
  +
{'heist_npcs.name', 'heist_jobs.name'},
  +
{
  +
join = 'heist_npc_skills.job_id=heist_jobs.id, heist_npc_skills.npc_id=heist_npcs.id',
  +
where = string.format('heist_npc_skills.job_id = "%s" AND heist_npc_skills.level >= %s', tpl_args.heist_required_job_id, tpl_args.heist_required_job_level),
  +
}
  +
)
  +
  +
local npcs = {}
  +
  +
for _, row in ipairs(results) do
  +
npcs[#npcs+1] = row['heist_npcs.name']
  +
end
  +
  +
tpl_args.heist_required_npcs = table.concat(npcs, ', ')
  +
tpl_args.heist_required_job = results[1]['heist_jobs.name']
  +
else
  +
tpl_args.heist_required_job = i18n.tooltips.heist_any_job
  +
end
  +
end
  +
end,
  +
},
  +
--
  +
-- 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 = core.factory.cast_text('prediction_text'),
  +
},
  +
seal_cost = {
  +
field = 'seal_cost',
  +
type = 'Integer',
  +
func = m_util.cast.factory.number('seal_cost'),
  +
},
  +
prophecy_reward = {
  +
field = 'reward',
  +
type = 'Text',
  +
func = core.factory.cast_text('prophecy_reward'),
  +
},
  +
prophecy_objective = {
  +
field = 'objective',
  +
type = 'Text',
  +
func = core.factory.cast_text('prophecy_objective'),
  +
},
  +
-- 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.card_art or 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._flags.is_prophecy then
  +
tpl_args.frame_type = 'prophecy'
  +
return
  +
end
  +
  +
local var = core.class_specifics[tpl_args.class_id]
  +
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 = tpl_args.rarity_id
  +
end,
  +
},
  +
--
  +
-- args populated by mod validation
  +
--
  +
mods = {
  +
default = function (tpl_args, frame) return {} end,
  +
func_fetch = function (tpl_args, frame)
  +
local results = m_cargo.query(
  +
{'items' ,'item_mods'},
  +
{'item_mods.id', 'item_mods.is_implicit', 'item_mods.is_random', 'item_mods.text'},
  +
{
  +
join = 'items._pageID=item_mods._pageID',
  +
where = string.format('items._pageName="%s" AND item_mods.is_implicit=1', tpl_args.base_item_page),
  +
}
  +
)
  +
for _, row in ipairs(results) do
  +
-- Handle text-only mods
  +
local result
  +
if row['item_mods.id'] == nil then
  +
result = row['item_mods.text']
  +
end
  +
tpl_args._mods[#tpl_args._mods+1] = {
  +
result=result,
  +
id=row['item_mods.id'],
  +
stat_text=row['item_mods.text'],
  +
is_implicit=m_util.cast.boolean(row['item_mods.is_implicit']),
  +
is_random=m_util.cast.boolean(row['item_mods.is_random']),
  +
}
  +
end
  +
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 _, dkey in ipairs(m_game.constants.damage_type_order) do
  +
dmg[key] = dmg[key] + tpl_args[string.format('%s_damage_%s_range_average', dkey, 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 _, dkey in ipairs(m_game.constants.damage_type_order) do
  +
local value = tpl_args[dkey .. '_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,
  +
},
  +
incubator_effect = {
  +
no_copy = true,
  +
field = 'effect',
  +
type = 'Text',
  +
func = nil,
  +
},
  +
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
  +
-- skip legacy maps in the drop html listing
  +
if not string.match(data['areas.id'], '^Map.*') or string.match(data['areas.id'], '^MapWorlds.*') or string.match(data['areas.id'], '^MapAtziri.*') then
  +
areas[#areas+1] = string.format('[[%s|%s]]', data['areas.main_page'] or data['areas._pageName'], data['areas.main_page'] or data['areas.name'])
  +
end
  +
end
  +
  +
tpl_args.drop_areas_html = table.concat(areas, ' • ')
  +
end,
  +
},
  +
release_version = {
  +
no_copy = true,
  +
field = 'release_version',
  +
type = 'String'
  +
},
  +
removal_version = {
  +
no_copy = true,
  +
field = 'removal_version',
  +
type = 'String',
  +
},
  +
--
  +
-- args governing use of the template itself
  +
--
  +
suppress_improper_modifiers_category = {
  +
no_copy = true,
  +
field = nil,
  +
func = m_util.cast.factory.boolean('suppress_improper_modifiers_category'),
  +
default = false,
  +
},
  +
upgraded_from_disabled = {
  +
no_copy = true,
  +
field = nil,
  +
func = m_util.cast.factory.boolean('upgraded_from_disabled'),
  +
default = false,
  +
},
  +
}
  +
  +
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',
  +
},
  +
},
  +
weapon_range = {
  +
field = 'weapon_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',
  +
},
  +
stats_override = {
  +
['local_weapon_no_physical_damage'] = {min=0, max=0},
  +
},
  +
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',
  +
},
  +
stats_override = {
  +
['local_weapon_no_physical_damage'] = {min=0, max=0},
  +
},
  +
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',
  +
'local_evasion_rating_and_energy_shield',
  +
},
  +
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',
  +
'local_evasion_rating_and_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},
  +
['local_no_attribute_requirements'] = {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},
  +
['local_no_attribute_requirements'] = {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},
  +
['local_no_attribute_requirements'] = {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_infobox = i18n.tooltips.physical_dps,
  +
html_fmt_options = {
  +
color = 'value',
  +
fmt = '%.1f',
  +
},
  +
},
  +
{
  +
name = 'fire_dps',
  +
field = 'fire_dps',
  +
damage_args = {'fire_damage'},
  +
label_infobox = i18n.tooltips.fire_dps,
  +
html_fmt_options = {
  +
color = 'fire',
  +
fmt = '%.1f',
  +
},
  +
},
  +
{
  +
name = 'cold_dps',
  +
field = 'cold_dps',
  +
damage_args = {'cold_damage'},
  +
label_infobox = i18n.tooltips.cold_dps,
  +
html_fmt_options = {
  +
color = 'cold',
  +
fmt = '%.1f',
  +
},
  +
},
  +
{
  +
name = 'lightning_dps',
  +
field = 'lightning_dps',
  +
damage_args = {'lightning_damage'},
  +
label_infobox = i18n.tooltips.lightning_dps,
  +
html_fmt_options = {
  +
color = 'lightning',
  +
fmt = '%.1f',
  +
},
  +
},
  +
{
  +
name = 'chaos_dps',
  +
field = 'chaos_dps',
  +
damage_args = {'chaos_damage'},
  +
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_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_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_infobox = i18n.tooltips.dps,
  +
html_fmt_options = {
  +
color = 'value',
  +
fmt = '%.1f',
  +
},
  +
},
  +
}
  +
  +
core.cargo = {}
  +
core.cargo.items = {
  +
table = 'items',
  +
fields = {
  +
html = core.map.html,
  +
html_extra = core.map.html_extra,
  +
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,
  +
class_id = core.map.class_id,
  +
rarity = core.map.rarity,
  +
rarity_id = core.map.rarity_id,
  +
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_monsters = core.map.drop_monsters,
  +
drop_text = core.map.drop_text,
  +
-- sightly different because of strange DB issues
  +
drop_rarity_ids = core.map.drop_rarities_ids,
  +
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,
  +
flavour_text_id = core.map.flavour_text_id,
  +
tags = core.map.tags,
  +
metadata_id = core.map.metadata_id,
  +
influences = core.map.influences,
  +
is_fractured = core.map.is_fractured,
  +
is_synthesised = core.map.is_synthesised,
  +
is_veiled = core.map.is_veiled,
  +
is_replica = core.map.is_replica,
  +
is_corrupted = core.map.is_corrupted,
  +
is_relic = core.map.is_relic,
  +
is_fated = core.map.is_fated,
  +
is_drop_restricted = core.map.is_drop_restricted,
  +
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,
  +
name_list = core.map.name_list,
  +
description = core.map.description,
  +
release_version = core.map.release_version,
  +
removal_version = core.map.removal_version,
  +
},
  +
}
  +
  +
core.cargo.item_sell_prices = {
  +
table = 'item_sell_prices',
  +
fields = {
  +
amount = {
  +
field = 'amount',
  +
type = 'Integer',
  +
},
  +
name = {
  +
field = 'name',
  +
type = 'String',
  +
},
  +
},
  +
}
  +
  +
  +
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_mods = {
  +
table = 'item_mods',
  +
fields = {
  +
id = {
  +
field = 'id',
  +
type = 'String',
  +
},
  +
stat_text = {
  +
field = 'text',
  +
type = 'Text',
  +
},
  +
is_implicit = {
  +
field = 'is_implicit',
  +
type = 'Boolean',
  +
},
  +
is_random = {
  +
field = 'is_random',
  +
type = 'Boolean',
  +
},
  +
},
  +
}
  +
  +
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',
  +
},
  +
is_random = {
  +
field = 'is_random',
  +
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,
  +
buff_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',
  +
},
  +
automatic = {
  +
field = 'automatic',
  +
type = 'Boolean',
  +
},
  +
}
  +
}
  +
  +
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,
  +
weapon_range = core.map.weapon_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,
  +
movement_speed = core.map.movement_speed,
  +
},
  +
}
  +
  +
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,
  +
-- 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,
  +
unique_guild_character = core.map.unique_map_guild_character,
  +
area_id = core.map.map_area_id,
  +
unique_area_id = core.map.unique_map_area_id,
  +
series = core.map.map_series,
  +
  +
-- REMOVE?
  +
area_level = core.map.map_area_level,
  +
unique_area_level = core.map.unique_map_area_level,
  +
},
  +
}
  +
  +
core.cargo.atlas_maps = {
  +
table = 'atlas_maps',
  +
fields = {
  +
x = core.map.atlas_x,
  +
y = core.map.atlas_y,
  +
region_id = core.map.atlas_region_id,
  +
region_minimum = core.map.atlas_region_minimum,
  +
x0 = core.map.atlas_x0,
  +
x1 = core.map.atlas_x1,
  +
x2 = core.map.atlas_x2,
  +
x3 = core.map.atlas_x3,
  +
x4 = core.map.atlas_x4,
  +
y0 = core.map.atlas_y0,
  +
y1 = core.map.atlas_y1,
  +
y2 = core.map.atlas_y2,
  +
y3 = core.map.atlas_y3,
  +
y4 = core.map.atlas_y4,
  +
map_tier0 = core.map.atlas_map_tier0,
  +
map_tier1 = core.map.atlas_map_tier1,
  +
map_tier2 = core.map.atlas_map_tier2,
  +
map_tier3 = core.map.atlas_map_tier3,
  +
map_tier4 = core.map.atlas_map_tier4,
  +
},
  +
}
  +
  +
core.cargo.atlas_connections = {
  +
table = 'atlas_connections',
  +
fields = {
  +
map1 = {
  +
field = 'map1',
  +
type = 'String',
  +
},
  +
map2 = {
  +
field = 'map2',
  +
type = 'String',
  +
},
  +
region0 = {
  +
field = 'region0',
  +
type = 'Boolean',
  +
},
  +
region1 = {
  +
field = 'region1',
  +
type = 'Boolean',
  +
},
  +
region2 = {
  +
field = 'region2',
  +
type = 'Boolean',
  +
},
  +
region3 = {
  +
field = 'region3',
  +
type = 'Boolean',
  +
},
  +
region4 = {
  +
field = 'region4',
  +
type = 'Boolean',
  +
},
  +
},
  +
}
  +
  +
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,
  +
type = core.map.essence_type,
  +
category = core.map.essence_category,
  +
},
  +
}
  +
  +
core.cargo.blight_items = {
  +
table = 'blight_items',
  +
fields = {
  +
tier = core.map.blight_item_tier,
  +
},
  +
}
  +
  +
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,
  +
},
  +
}
  +
  +
core.cargo.incubators = {
  +
table = 'incubators',
  +
fields = {
  +
effect = core.map.incubator_effect,
  +
},
  +
}
  +
  +
core.cargo.harvest_seeds = {
  +
table = 'harvest_seeds',
  +
fields = {
  +
effect = core.map.seed_effect,
  +
type_id = core.map.seed_type_id,
  +
type = core.map.seed_type,
  +
tier = core.map.seed_tier,
  +
growth_cycles = core.map.seed_growth_cycles,
  +
required_nearby_seed_tier = core.map.seed_required_nearby_seed_tier,
  +
required_nearby_seed_amount = core.map.seed_required_nearby_seed_amount,
  +
consumed_wild_lifeforce_percentage = core.map.seed_consumed_wild_lifeforce_percentage,
  +
consumed_vivid_lifeforce_percentage = core.map.seed_consumed_vivid_lifeforce_percentage,
  +
consumed_primal_lifeforce_percentage = core.map.seed_consumed_primal_lifeforce_percentage,
  +
granted_craft_option_ids = core.map.seed_granted_craft_option_ids,
  +
},
  +
}
  +
  +
core.cargo.harvest_plant_boosters = {
  +
table = 'harvest_plant_boosters',
  +
fields = {
  +
radius = core.map.plant_booster_radius,
  +
lifeforce = core.map.plant_booster_lifeforce,
  +
additional_crafting_options = core.map.plant_booster_additional_crafting_options,
  +
extra_chances = core.map.plant_booster_extra_chances,
  +
},
  +
}
  +
  +
core.cargo.heist_equipment = {
  +
table = 'heist_equipment',
  +
fields = {
  +
required_job_id = core.map.heist_required_job_id,
  +
required_job_level = core.map.heist_required_job_level,
  +
},
  +
}
  +
  +
core.cargo.quest_rewards = {
  +
table = 'quest_rewards',
  +
fields = {
  +
quest = {
  +
field = 'quest',
  +
type = 'String',
  +
},
  +
quest_id = {
  +
field = 'quest_id',
  +
type = 'String',
  +
},
  +
-- still needed?
  +
act = {
  +
field = 'act',
  +
type = 'Integer',
  +
},
  +
classes = {
  +
field = 'classes',
  +
type = 'List (,) of String',
  +
},
  +
class_ids = {
  +
field = 'class_ids',
  +
type = 'List (,) of String',
  +
},
  +
sockets = {
  +
field = 'sockets',
  +
type = 'Integer',
  +
},
  +
item_level = {
  +
field = 'item_level',
  +
type = 'Integer',
  +
},
  +
rarity = {
  +
field = 'rarity',
  +
type = 'String',
  +
},
  +
notes = {
  +
field = 'notes',
  +
type = 'Text',
  +
},
  +
},
  +
}
  +
  +
core.cargo.vendor_rewards = {
  +
table = 'vendor_rewards',
  +
fields = {
  +
quest = {
  +
field = 'quest',
  +
type = 'String',
  +
},
  +
quest_id = {
  +
field = 'quest_id',
  +
type = 'String',
  +
},
  +
act = {
  +
field = 'act',
  +
type = 'Integer',
  +
},
  +
npc = {
  +
field = 'npc',
  +
type = 'String',
  +
},
  +
classes = {
  +
field = 'classes',
  +
type = 'List (,) of String',
  +
},
  +
class_ids = {
  +
field = 'class_ids',
  +
type = 'List (,) of String',
  +
},
  +
}
  +
}
  +
  +
-- 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
  +
-- drop_areas after tags
  +
-- is_relic after rarity
  +
core.default_args = {
  +
'rarity_id', 'rarity', 'name', 'name_list', 'size_x', 'size_y',
  +
'drop_rarities_ids', 'drop_rarities', 'drop_enabled', 'drop_level', 'drop_level_maximum', 'drop_leagues', 'drop_text', 'required_level', 'required_level_final',
  +
'inventory_icon', 'alternate_art_inventory_icons', 'flavour_text', 'flavour_text_id',
  +
'cannot_be_traded_or_modified', 'help_text', 'tags', 'metadata_id', 'influences', 'is_fractured', 'is_synthesised', 'is_veiled', 'is_replica', 'is_corrupted', 'is_relic', 'is_fated', 'purchase_costs', 'sell_prices_override', 'mods',
  +
'drop_areas', 'drop_areas_html', 'drop_monsters',
  +
  +
'upgraded_from_disabled',
  +
'suppress_improper_modifiers_category',
  +
  +
'is_prophecy', 'is_blight_item',
  +
  +
'class',
  +
'is_drop_restricted',
  +
}
  +
-- 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 = {['LifeFlask'] = true, ['ManaFlask'] = true, ['HybridFlask'] = true, ['UtilityFlask'] = true, ['UtilityFlaskCritical'] = true},
  +
args = {'quality', 'flask_duration', 'charges_max', 'charges_per_use'},
  +
},
  +
weapons = {
  +
tables = {'weapons'},
  +
keys = {['Claw'] = true, ['Dagger'] = true, ['Wand'] = true, ['One Hand Sword'] = true, ['Thrusting One Hand Sword'] = true, ['One Hand Axe'] = true, ['One Hand Mace'] = true, ['Bow'] = true, ['Staff'] = true, ['Two Hand Sword'] = true, ['Two Hand Axe'] = true, ['Two Hand Mace'] = true, ['Sceptre'] = true, ['FishingRod'] = true, ['Rune Dagger'] = true, ['Warstaff'] = 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', 'weapon_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 Gem'] = true, ['Support Skill Gem'] = true},
  +
args = {'dexterity_percent', 'strength_percent', 'intelligence_percent', 'primary_attribute', 'gem_tags'},
  +
},
  +
armor = {
  +
tables = {'armours'},
  +
keys = {['Gloves'] = true, ['Boots'] = true, ['Body Armour'] = true, ['Helmet'] = true, ['Shield'] = true},
  +
args = {'quality', 'required_dexterity', 'required_intelligence', 'required_strength', 'armour', 'energy_shield', 'evasion', 'movement_speed'},
  +
},
  +
stackable = {
  +
tables = {'stackables'},
  +
keys = {['Currency'] = true, ['StackableCurrency'] = true, ['HideoutDoodad'] = true, ['Microtransaction'] = true, ['DivinationCard'] = true, ['DelveSocketableCurrency'] = true, ['DelveStackableSocketableCurrency'] = true, ['Incubator'] = true, ['HarvestSeed'] = true, ['HarvestPlantBooster'] = true},
  +
args = {'stack_size', 'stack_size_currency_tab', 'description', 'cosmetic_type'},
  +
},
  +
heist_equipment = {
  +
tables = {'heist_equipment'},
  +
keys = {['HeistEquipmentWeapon'] = true, ['HeistEquipmentTool'] = true, ['HeistEquipmentUtility'] = true, ['HeistEquipmentReward'] = true},
  +
args = {'heist_required_job_id', 'heist_required_job_level', 'heist_data'},
  +
},
  +
}
  +
  +
core.class_specifics = {
  +
['Amulet'] = {
  +
tables = {'amulets'},
  +
args = {'is_talisman', 'talisman_tier'},
  +
},
  +
['LifeFlask'] = {
  +
args = {'flask_life'},
  +
},
  +
['ManaFlask'] = {
  +
args = {'flask_mana'},
  +
},
  +
['HybridFlask'] = {
  +
args = {'flask_life', 'flask_mana'},
  +
},
  +
['UtilityFlask'] = {
  +
tables = {'item_buffs'},
  +
args = {'buff_id', 'buff_values', 'buff_stat_text', 'buff_icon'},
  +
},
  +
['UtilityFlaskCritical'] = {
  +
tables = {'item_buffs'},
  +
args = {'buff_id', 'buff_values', 'buff_stat_text', 'buff_icon'},
  +
},
  +
['Active Skill Gem'] = {
  +
defaults = {
  +
help_text = i18n.help_text_defaults.active_gem,
  +
size_x = 1,
  +
size_y = 1,
  +
},
  +
frame_type = 'gem',
  +
},
  +
['Support Skill Gem'] = {
  +
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',
  +
},
  +
['Shield'] = {
  +
tables = {'shields'},
  +
args = {'block'},
  +
},
  +
['Map'] = {
  +
tables = {'maps', 'atlas_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',
  +
'map_series',
  +
  +
'atlas_x',
  +
'atlas_y',
  +
'atlas_region_id',
  +
'atlas_x0',
  +
'atlas_x1',
  +
'atlas_x2',
  +
'atlas_x3',
  +
'atlas_x4',
  +
'atlas_y0',
  +
'atlas_y1',
  +
'atlas_y2',
  +
'atlas_y3',
  +
'atlas_y4',
  +
'atlas_map_tier0',
  +
'atlas_map_tier1',
  +
'atlas_map_tier2',
  +
'atlas_map_tier3',
  +
'atlas_map_tier4',
  +
'atlas_connections', -- pseudo to fill connections table
  +
},
  +
skip_stat_lines = i18n.stat_skip_patterns.maps,
  +
},
  +
['Currency'] = {
  +
frame_type = 'currency',
  +
},
  +
['StackableCurrency'] = {
  +
tables = {'essences', 'prophecies', 'blight_items'},
  +
args = {'is_essence', 'essence_level_restriction', 'essence_level', 'essence_type', 'essence_category', 'blight_item_tier'},
  +
frame_type = 'currency',
  +
},
  +
['Microtransaction'] = {
  +
frame_type = 'currency',
  +
},
  +
['HideoutDoodad'] = {
  +
tables = {'hideout_doodads'},
  +
args = {'is_master_doodad', 'master', 'master_level_requirement', 'master_favour_cost', 'variation_count'},
  +
frame_type = 'currency',
  +
},
  +
['Jewel'] = {
  +
tables = {'jewels'},
  +
late_args = {'item_limit', 'jewel_radius_html'},
  +
defaults = {
  +
help_text = i18n.help_text_defaults.jewel,
  +
},
  +
skip_stat_lines = i18n.stat_skip_patterns.jewels,
  +
},
  +
['AbyssJewel'] = {
  +
tables = {'jewels'},
  +
late_args = {'item_limit', 'jewel_radius_html'},
  +
skip_stat_lines = i18n.stat_skip_patterns.jewels,
  +
},
  +
['QuestItem'] = {
  +
args = {'description'},
  +
frame_type = 'quest',
  +
},
  +
['DivinationCard'] = {
  +
tables = {'divination_cards'},
  +
args = {'card_art',},
  +
frame_type = 'divicard',
  +
},
  +
['LabyrinthItem'] = {
  +
frame_type = 'currency',
  +
},
  +
['LabyrinthTrinket'] = {
  +
tables = {'item_buffs'},
  +
args = {'description', 'buff_icon'},
  +
frame_type = 'currency',
  +
},
  +
['PantheonSoul'] = {
  +
defaults = {
  +
cannot_be_traded_or_modified = true,
  +
},
  +
},
  +
['IncursionItem'] = {
  +
frame_type = 'currency',
  +
},
  +
['Incubator'] = {
  +
tables = {'incubators'},
  +
args = {'incubator_effect'},
  +
frame_type = 'currency',
  +
},
  +
['HarvestSeed'] = {
  +
tables = {'harvest_seeds'},
  +
args = {'seed_effect', 'seed_type_id', 'seed_effect', 'seed_type', 'seed_type_html', 'seed_tier', 'seed_growth_cycles', 'seed_required_nearby_seed_tier', 'seed_required_nearby_seed_amount', 'seed_consumed_wild_lifeforce_percentage', 'seed_consumed_vivid_lifeforce_percentage', 'seed_consumed_primal_lifeforce_percentage', 'seed_granted_craft_option_ids'},
  +
frame_type = 'currency',
  +
},
  +
['HarvestPlantBooster'] = {
  +
tables = {'harvest_plant_boosters'},
  +
args = {'plant_booster_radius', 'plant_booster_lifeforce', 'plant_booster_additional_crafting_options', 'plant_booster_extra_chances'},
  +
frame_type = 'currency',
  +
},
  +
['HeistObjective'] = {
  +
frame_type = 'currency',
  +
},
  +
}
  +
  +
-- 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)
  +
  +
core.item_classes[tpl_args.class_id] = {
  +
tables = {},
  +
args = {},
  +
late_args = {},
  +
defaults = {},
  +
}
  +
  +
for _, table_name in ipairs(core.tables) do
  +
table.insert(core.item_classes[tpl_args.class_id].tables, table_name)
  +
end
  +
  +
for _, row in pairs(core.class_groups) do
  +
for class, _ in pairs(row.keys) do
  +
if class == tpl_args.class_id then
  +
for _, k in ipairs(core.item_classes_extend) do
  +
if row[k] ~= nil then
  +
for _, value in ipairs(row[k]) do
  +
table.insert(core.item_classes[tpl_args.class_id][k], value)
  +
end
  +
end
  +
end
  +
break
  +
end
  +
end
  +
end
  +
  +
local class_specifics = core.class_specifics[tpl_args.class_id]
  +
if class_specifics then
  +
for _, k in ipairs(core.item_classes_extend) do
  +
if class_specifics[k] ~= nil then
  +
for _, value in ipairs(class_specifics[k]) do
  +
table.insert(core.item_classes[tpl_args.class_id][k], value)
  +
end
  +
end
  +
end
  +
if class_specifics.defaults ~= nil then
  +
for key, value in pairs(class_specifics.defaults) do
  +
core.item_classes[tpl_args.class_id].defaults[key] = value
  +
end
  +
end
 
end
 
end
 
end
 
end
   
  +
-- GroupTable -> RowTable -> formatter function
function h.factory.display_raw_value(key)
 
  +
--
return function(tpl_args, frame)
 
  +
--
return tpl_args[key]
 
  +
  +
h.conditions = {}
  +
h.conditions.factory = {}
  +
  +
function h.conditions.factory.arg (args)
  +
-- Обязательно:
  +
-- arg: Аргумент для проверки (The argument to check against)
  +
-- Один должен быть указан (One must be specified)
  +
-- value: проверяет, равен ли аргумент этому значению (check whether the argument equals this value)
  +
-- values: проверяет, находится ли аргумент в этом списке значений (check whether the argument is in this list of values)
  +
-- values_assoc: проверяет, находится ли аргумент в этой ассоциативной таблице (check whether the argument is in this associative table)
  +
--
  +
-- Опционально:
  +
-- negate: отрицает проверку против значения, т.е. не равно ли значение или нет в списке/таблице (negates the check against the value, i.e. whether the value is not equal or not in the list/table).
  +
if args.negate == nil then
  +
args.negate = false
 
end
 
end
  +
  +
-- Inner type of function depending on whether to check a single value, a list of values or an associative list of values
  +
local inner
  +
if args.value ~= nil then
  +
inner = function (tpl)
  +
return tpl == args.value
  +
end
  +
elseif args.values ~= nil then
  +
inner = function (tpl)
  +
for _, value in ipairs(args.values) do
  +
if tpl == value then
  +
return true
  +
end
  +
end
  +
return false
  +
end
  +
elseif args.values_assoc ~= nil then
  +
inner = function(tpl)
  +
return args.values_assoc[tpl] ~= nil
  +
end
  +
else
  +
error(string.format('Missing inner comparision function. Args: %s', mw.dumpObject(args)))
  +
end
  +
  +
-- Внешний тип функции в зависимости от того, следует ли проверять одно значение или сверяться с таблицей (Outer type of function depending on whether to check a single value or against a table)
  +
return function (tpl_args, frame)
  +
local tpl_value = tpl_args[args.arg]
  +
local rtr
  +
if type(tpl_value) == 'table' then
  +
rtr = false
  +
for key, value in pairs(tpl_value) do
  +
if type(key) == 'number' then
  +
rtr = rtr or inner(value)
  +
else
  +
rtr = rtr or inner(key)
  +
end
  +
end
  +
  +
else
  +
rtr = inner(tpl_value)
  +
end
  +
  +
if args.negate then
  +
rtr = not rtr
  +
end
  +
  +
return rtr
  +
end
 
end
 
end
   
function h.factory.descriptor_value(args)
+
function h.conditions.factory.league (args)
  +
return function (tpl_args, frame)
-- Arguments:
 
  +
for _, league in ipairs(tpl_args.drop_leagues or {}) do
-- key
 
  +
if league == args.league then
-- tbl
 
args = args or {}
+
return true
  +
end
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
 
end
return value
+
return false
 
end
 
end
 
end
 
end
   
  +
h.conditions.normal = h.conditions.factory.arg{arg='rarity_id', value='normal'}
-- ----------------------------------------------------------------------------
 
  +
h.conditions.unique = h.conditions.factory.arg{arg='rarity_id', value='unique'}
-- Additional configuration
 
-- ----------------------------------------------------------------------------
 
   
  +
-- По умолчанию для всех записей, но может быть отключен для определенных (Default for all entries, but can be disabled by specific ones).
-- helper to loop over the range variables easier
 
  +
core.automatic_upgraded_from_defaults = {
c.range_map = {
 
  +
is_drop_restricted = h.conditions.factory.arg{arg='is_drop_restricted', value=false},
min = {
 
  +
is_corrupted = h.conditions.factory.arg{arg='is_corrupted', value=false},
var = '_range_minimum',
 
  +
}
  +
-- Порядок имеет значение (Order matters)
  +
-- Поставьте наиболее конкретный результат вверху и наименее конкретный внизу (Put most specific outcome at the topic and the least specific at the bottom)
  +
core.automatic_upgraded_from = {
  +
--[[
  +
{
  +
defaults = {
  +
arg_key = function (tpl_args, frame) end,
  +
},
  +
condition = {
  +
function (tpl_args, frame) end,
  +
},
  +
text = '',
  +
groups = {
  +
{
  +
name = '',
  +
item_id = '',
  +
amount = 0,
  +
notes = '',
  +
},
  +
},
 
},
 
},
max = {
+
]]
  +
--
var = '_range_maximum',
 
  +
-- Item base specific
  +
--
  +
{
  +
defaults = {
  +
is_corrupted = false,
  +
},
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='base_item_id', value='Metadata/Items/Amulets/Amulet9'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_corrupted, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.agate_amulet))
  +
end,
  +
groups = {
  +
{
  +
-- Передышка Лисы
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardLysahsRespite",
  +
amount = 6,
  +
},
  +
},
 
},
 
},
avg = {
+
{
var = '_range_average',
+
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='base_item_id', value='Metadata/Items/Rings/Ring15'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_3, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.unset_ring))
  +
end,
  +
groups = {
  +
{
  +
-- Кающийся
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardThePenitent",
  +
amount = 5,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='base_item_id', value='Metadata/Items/Rings/Ring4'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_3, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.gold_ring))
  +
end,
  +
groups = {
  +
{
  +
-- Проблеск надежды
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardGlimmerOfHope",
  +
amount = 8,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_corrupted = false,
  +
},
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='base_item_id', value='Metadata/Items/Rings/Ring8'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_corrupted_3, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.prismatic_ring))
  +
end,
  +
groups = {
  +
{
  +
-- Надежда
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardHope",
  +
amount = 5,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='metadata_id', values={'Metadata/Items/Rings/Ring12', 'Metadata/Items/Rings/Ring13', 'Metadata/Items/Rings/Ring14'}},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_3, m_util.html.poe_color('rare', i18n.upgraded_from_tooltips.two_stone_ring))
  +
end,
  +
groups = {
  +
{
  +
-- Утраченная любовь Лантадора
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardLantadorsLostLove",
  +
amount = 7,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='base_item_id', values={'Metadata/Items/Rings/Ring12', 'Metadata/Items/Rings/Ring13', 'Metadata/Items/Rings/Ring14'}},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_3, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.two_stone_ring))
  +
end,
  +
groups = {
  +
{
  +
-- Гетерохромия
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardHeterochromia",
  +
amount = 2,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='base_item_id', value='Metadata/Items/Weapons/OneHandWeapons/OneHandMaces/Sceptre11'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.crystal_sceptre))
  +
end,
  +
groups = {
  +
{
  +
-- Свет и правда
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardLightAndTruth",
  +
amount = 2,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='base_item_id', value='Metadata/Items/Flasks/FlaskUtility5'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.granite_flask))
  +
end,
  +
groups = {
  +
{
  +
-- Пьющий землю
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardEarthDrinker",
  +
amount = 5,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='base_item_id', value='Metadata/Items/Armours/Helmets/HelmetStrDex10'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.nightmare_bascinet))
  +
end,
  +
groups = {
  +
{
  +
-- Гладиатор
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheGladiator",
  +
amount = 5,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='base_item_id', value='Metadata/Items/Belts/Belt1'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.rustic_sash))
  +
end,
  +
groups = {
  +
{
  +
-- Противоборство
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheStandoff",
  +
amount = 3,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
},
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='base_item_id', value='Metadata/Items/Jewels/JewelTimeless'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.timeless_jewel))
  +
end,
  +
groups = {
  +
{
  +
-- Безмятежные минуты
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardPeacefulMoments",
  +
amount = 5,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_corrupted = false,
  +
},
  +
condition = {
  +
h.conditions.normal,
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Карта храма ваал')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(m_util.html.poe_color('rare', string.format('Карта храма ваал')))
  +
end,
  +
groups = {
  +
{
  +
-- Пережитки прошлого
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardLingeringRemnants",
  +
amount = 16,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_corrupted = false,
  +
},
  +
condition = {
  +
h.conditions.normal,
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Карта террасы')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(m_util.html.poe_color('rare', string.format('Карта террасы')))
  +
end,
  +
groups = {
  +
{
  +
-- Лёгкая прогулка
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheEasyStroll",
  +
amount = 2,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.normal,
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Карта колизея')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(m_util.html.poe_color('normal', string.format('Карта колизея')))
  +
end,
  +
groups = {
  +
{
  +
-- Чемпион Арены
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheArenaChampion",
  +
amount = 10,
  +
},
  +
},
  +
},
  +
--
  +
-- Even more specific stuff
  +
--
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
is_corrupted = false,
  +
},
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='is_talisman', value=true},
  +
h.conditions.factory.arg{arg='talisman_tier', value=1},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_corrupted, m_util.html.poe_color('rare', i18n.upgraded_from_tooltips.tier_1_talisman))
  +
end,
  +
groups = {
  +
{
  +
-- Призыв Первых
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardCallToTheFirstOnes",
  +
amount = 5,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
is_corrupted = false,
  +
},
  +
condition = {
  +
h.conditions.factory.arg{arg='is_talisman', value=true},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, i18n.upgraded_from_tooltips.talisman)
  +
end,
  +
groups = {
  +
{
  +
-- Первобытный инкубатор
  +
item_id = "Metadata/Items/Currency/CurrencyIncubationTalismans",
  +
amount = 1,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Атзири')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Атзири')))
  +
end,
  +
groups = {
  +
{
  +
-- Поклонник
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheAdmirer",
  +
amount = 9,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Доэдре')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Доэдре')))
  +
end,
  +
groups = {
  +
{
  +
-- Безумие Доэдре
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardDoedresMadness",
  +
amount = 9,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Шавронн')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Шавронн')))
  +
end,
  +
groups = {
  +
{
  +
-- Эстет
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheAesthete",
  +
amount = 8,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
},
  +
condition = {
  +
h.conditions.unique,
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Ригвальда')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Ригвальда')))
  +
end,
  +
groups = {
  +
{
  +
-- Волк
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheWolf",
  +
amount = 5,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Львиного глаза')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Львиного глаза')))
  +
end,
  +
groups = {
  +
{
  +
-- Лев
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheLion",
  +
amount = 5,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
},
  +
condition = {
  +
h.conditions.unique,
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Фаррул')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Фаррул')))
  +
end,
  +
groups = {
  +
{
  +
-- Кошачий совет
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardCouncilOfCats",
  +
amount = 4,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.arg{arg='class_id', value='Jewel'},
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Первородн')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_primordial, m_util.html.poe_color('unique', i18n.upgraded_from_tooltips.jewel))
  +
end,
  +
groups = {
  +
{
  +
-- Первородный
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardThePrimordial",
  +
amount = 5,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
is_corrupted = false,
  +
},
  +
condition = {
  +
h.conditions.unique,
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.name, 'Знак предтечи')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_corrupted, m_util.html.poe_color('unique', string.format('Знак предтечи')))
  +
end,
  +
groups = {
  +
{
  +
-- Поминовение
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardRemembrance",
  +
amount = 8,
  +
},
  +
},
  +
},
  +
--
  +
-- League specific items
  +
--
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.league{league='Немезида'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Немезиды')))
  +
end,
  +
groups = {
  +
{
  +
-- Валькирия
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheValkyrie",
  +
amount = 8,
  +
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_corrupted = false,
  +
},
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.league{league='Немезида'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_corrupted, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Немезиды')))
  +
end,
  +
groups = {
  +
{
  +
-- Неустрашимый
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheUndaunted",
  +
amount = 5,
  +
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.league{league='Иномирье'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Иномирья')))
  +
end,
  +
groups = {
  +
{
  +
-- Призыв
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheCalling",
  +
amount = 6,
  +
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
},
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.league{league='Разлом'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Разлома')))
  +
end,
  +
groups = {
  +
{
  +
-- Разлом
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheBreach",
  +
amount = 4,
  +
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.league{league='Спуск'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Спуска')))
  +
end,
  +
groups = {
  +
{
  +
-- Один в темноте
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardAloneInTheDarkness",
  +
amount = 5,
  +
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
},
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.league{league='Бестиарий'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Бестиария')))
  +
end,
  +
groups = {
  +
{
  +
-- Дар Первых
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardBoonOfTheFirstOnes",
  +
amount = 6,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
},
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.league{league='Бездна'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('rare', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Бездны')))
  +
end,
  +
groups = {
  +
{
  +
-- Инкубатор Бездны
  +
item_id = "Metadata/Items/Currency/CurrencyIncubationAbyss",
  +
amount = 1,
  +
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
},
  +
condition = {
  +
h.conditions.unique,
  +
h.conditions.factory.league{league='Метаморф'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('unique', string.format(i18n.upgraded_from_tooltips.f.x_item, 'Метаморфа')))
  +
end,
  +
groups = {
  +
{
  +
-- Преследующие тени
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardHauntingShadows",
  +
amount = 4,
  +
},
  +
},
  +
},
  +
--
  +
-- Subset of class
  +
--
  +
{
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='class_id', value='MapFragment'},
  +
h.conditions.factory.arg{arg='tags', value='atziri1'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('normal', i18n.upgraded_from_tooltips.sacrifice_fragment))
  +
end,
  +
groups = {
  +
{
  +
-- Её маска
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardHerMask",
  +
amount = 4,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
},
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='class_id', value='MapFragment'},
  +
h.conditions.factory.arg{arg='tags', value='atziri2'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('normal', i18n.upgraded_from_tooltips.mortal_fragment))
  +
end,
  +
groups = {
  +
{
  +
-- Клятва Самбодхи
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardSambodhisVow",
  +
amount = 3,
  +
},
  +
},
  +
},
  +
{
  +
defaults = {
  +
is_drop_restricted = false,
  +
},
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='class_id', value='MapFragment'},
  +
function (tpl_args, frame)
  +
return string.find(tpl_args.metadata_id, 'UberElderFragment')
  +
end,
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('normal', i18n.upgraded_from_tooltips.uber_elder_fragment))
  +
end,
  +
groups = {
  +
{
  +
-- Аномальное увядание
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheEldritchDecay",
  +
amount = 4,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='class_id', value='MapFragment'},
  +
h.conditions.factory.arg{arg='tags', value='breachstone4'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('normal', i18n.upgraded_from_tooltips.pure_breachstone))
  +
end,
  +
groups = {
  +
{
  +
-- Сделка
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheBargain",
  +
amount = 5,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='class_id', value='MapFragment'},
  +
h.conditions.factory.arg{arg='tags', value='breachstone'},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random, m_util.html.poe_color('normal', i18n.upgraded_from_tooltips.breachstone))
  +
end,
  +
groups = {
  +
{
  +
-- Скрытный
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheObscured",
  +
amount = 7,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='class_id', value='StackableCurrency'},
  +
h.conditions.factory.arg{arg='is_essence', value=true},
  +
h.conditions.factory.arg{arg='essence_level', value=7},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_x_amount_2, m_util.html.poe_color('currency', i18n.upgraded_from_tooltips.deafening_essence), 3)
  +
end,
  +
groups = {
  +
{
  +
-- Какофония
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardTheCacophony",
  +
amount = 8,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='class_id', value='StackableCurrency'},
  +
h.conditions.factory.arg{arg='is_essence', value=true},
  +
h.conditions.factory.arg{arg='essence_level', value=6},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_x_amount_2, m_util.html.poe_color('currency', i18n.upgraded_from_tooltips.shrieking_essence), 9)
  +
end,
  +
groups = {
  +
{
  +
-- Гармония душ
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardHarmonyOfSouls",
  +
amount = 9,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='class_id', value='StackableCurrency'},
  +
h.conditions.factory.arg{arg='is_essence', value=true},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_x_amount_2, m_util.html.poe_color('currency', i18n.upgraded_from_tooltips.essence), 3)
  +
end,
  +
groups = {
  +
{
  +
-- Три голоса
  +
item_id = "Metadata/Items/DivinationCards/DivinationCardThreeVoices",
  +
amount = 3,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='class_id', value='StackableCurrency'},
  +
h.conditions.factory.arg{arg='is_essence', value=true},
  +
},
  +
text = function (tpl_args, frame)
  +
return string.format(i18n.upgraded_from_tooltips.f.random_2, m_util.html.poe_color('currency', i18n.upgraded_from_tooltips.essence))
  +
end,
  +
groups = {
  +
{
  +
-- Шепчущий инкубатор
  +
item_id = "Metadata/Items/Currency/CurrencyIncubationEssence",
  +
amount = 1,
  +
},
  +
},
  +
},
  +
{
  +
condition = {
  +
h.conditions.normal,
  +
h.conditions.factory.arg{arg='class_id', value='StackableCurrency'},
  +