Module:Item/recipes: Difference between revisions

From Path of Exile 2 Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
 
(135 intermediate revisions by 6 users not shown)
Line 1: Line 1:
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--  
--  
-- Upgrade paths for Module:Item2
-- Recipes for Module:Item
--  
--  
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------


local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')
local m_cargo = require('Module:Cargo')
local m_util = require('Module:Util')


local m_game = mw.loadData('Module:Game')
local m_game = mw.loadData('Module:Game')
Line 14: Line 14:


-- Should we use the sandbox version of our submodules?
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox()
local use_sandbox = m_util.misc.maybe_sandbox('Item')


-- The cfg table contains all localisable strings and configuration, to make it
-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Item2/config/sandbox') or mw.loadData('Module:Item2/config')
local cfg = use_sandbox and mw.loadData('Module:Item/config/sandbox') or mw.loadData('Module:Item/config')


local i18n = cfg.i18n.upgrade_paths
local i18n = cfg.i18n.recipes


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
Line 49: Line 49:
     -- Optional:
     -- Optional:
     --  negate: negates the check against the value, i.e. whether the value is not equal or not in the list/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 = args or {}
        args.negate = false
    end
      
      
     -- Inner type of function depending on whether to check a single value, a list of values or an associative list of values
     -- Inner type of function depending on whether to check a single value, a list of values or an associative list of values
Line 77: Line 75:
      
      
     -- Outer type of function depending on whether to check a single value or against a table
     -- Outer type of function depending on whether to check a single value or against a table
     return function (tpl_args, frame)
     return function (tpl_args)
         local tpl_value = tpl_args[args.arg]
         local tpl_value = tpl_args[args.arg]
         local rtr
         local rtr
Line 99: Line 97:
end
end


function h.conditions.factory.league(args)
function h.conditions.factory.not_arg(args)
     return function (tpl_args, frame)
    args = args or {}
         for _, league in ipairs(tpl_args.drop_leagues or {}) do
    args.negate = true
             if league == args.league then
    return h.conditions.factory.arg(args)
end
 
function h.conditions.factory.flag_is_set(args)
     return function (tpl_args)
        return tpl_args._flags[args.flag] == true
    end
end
 
function h.conditions.factory.acquisition_tag(args)
    return function (tpl_args)
        local negate = args.negate or false
        for _, tag in ipairs(tpl_args.acquisition_tags or {}) do
            if tag == args.tag then
                return not negate
            end
        end
        return negate
    end
end
 
function h.conditions.factory.drop_monsters(args)
    return function (tpl_args)
        for _, monster in ipairs(tpl_args.drop_monsters or {}) do
            if string.find(monster, args.monster, 1, true) then
                return true
            end
        end
        return false
    end
end
 
function h.conditions.factory.drop_rarity(args)
    return function (tpl_args)
         for _, rarity in ipairs(tpl_args.drop_rarities_ids or {}) do
             if rarity == args.rarity then
                 return true
                 return true
             end
             end
Line 110: Line 143:
end
end


h.conditions.normal = h.conditions.factory.arg{arg='rarity_id', value='normal'}
function h.conditions.factory.drop_level_not_greater_than(args)
h.conditions.unique = h.conditions.factory.arg{arg='rarity_id', value='unique'}
    return function (tpl_args)
        if tpl_args.drop_level == nil then
            return true
        end
        return tpl_args.drop_level <= args.level
    end
end


function h.conditions.item_class_has_corrupted_implicits(tpl_args, frame)
function h.conditions.item_class_has_corrupted_implicits(tpl_args)
     local groups = {
     local groups = {
         cfg.class_groups.weapons.keys,
         cfg.class_groups.weapons.keys,
Line 121: Line 160:
     }
     }
     for _, g in ipairs(groups) do
     for _, g in ipairs(groups) do
         if h.conditions.factory.arg{arg='class_id', values_assoc=g}(tpl_args, frame) then
         if h.conditions.factory.arg{arg='class_id', values_assoc=g}(tpl_args) then
             return true
             return true
         end
         end
Line 128: Line 167:
end
end


function h.conditions.item_class_has_influences(tpl_args, frame)
function h.conditions.item_class_has_influences(tpl_args)
     local groups = {
     local groups = {
         cfg.class_groups.weapons.keys,
         cfg.class_groups.weapons.keys,
Line 136: Line 175:
     }
     }
     for _, g in ipairs(groups) do
     for _, g in ipairs(groups) do
         if h.conditions.factory.arg{arg='class_id', values_assoc=g}(tpl_args, frame) and h.conditions.factory.arg{arg='class_id', value='FishingRod', negate=true}(tpl_args, frame) then
         if h.conditions.factory.arg{arg='class_id', values_assoc=g}(tpl_args) and h.conditions.factory.not_arg{arg='class_id', value='FishingRod'}(tpl_args) then
            return true
        end
    end
    return false
end
 
function h.conditions.item_class_has_synthesised_implicits(tpl_args)
    local groups = {
        cfg.class_groups.weapons.keys,
        cfg.class_groups.armor.keys,
        cfg.class_groups.jewellery.keys,
        {['Quiver'] = true, ['Jewel'] = true, ['AbyssJewel'] = true},
    }
    for _, g in ipairs(groups) do
        if h.conditions.factory.arg{arg='class_id', values_assoc=g}(tpl_args) and h.conditions.factory.not_arg{arg='class_id', value='FishingRod'}(tpl_args) then
            return true
        end
    end
    return false
end
 
function h.conditions.item_class_has_fractured_modifiers(tpl_args)
    local groups = {
        cfg.class_groups.weapons.keys,
        cfg.class_groups.armor.keys,
        cfg.class_groups.jewellery.keys,
        {['Quiver'] = true, ['Jewel'] = true, ['Map'] = true},
    }
    for _, g in ipairs(groups) do
        if h.conditions.factory.arg{arg='class_id', values_assoc=g}(tpl_args) and h.conditions.factory.not_arg{arg='class_id', value='FishingRod'}(tpl_args) then
             return true
             return true
         end
         end
Line 149: Line 218:
local c = {}
local c = {}


-- Default for all entries, but can be disabled by specific ones.
c.named_conditions = {
c.automatic_upgraded_from_defaults = {
    is_normal = h.conditions.factory.arg{arg='rarity_id', value='normal'},
     is_drop_restricted = h.conditions.factory.arg{arg='is_drop_restricted', value=false},
    is_unique = h.conditions.factory.arg{arg='rarity_id', value='unique'},
     is_corrupted = h.conditions.factory.arg{arg='is_corrupted', value=false},
     is_not_drop_restricted = h.conditions.factory.arg{arg='is_drop_restricted', value=false},
     is_replica = h.conditions.factory.arg{arg='is_replica', value=false},
     is_not_corrupted = h.conditions.factory.arg{arg='is_corrupted', value=false},
     is_not_replica = h.conditions.factory.arg{arg='is_replica', value=false},
    drop_level_ngt_divcard_default_max_ilvl = h.conditions.factory.drop_level_not_greater_than{level=cfg.divination_card_exchange_default_max_ilvl},
    item_class_has_corrupted_implicits = h.conditions.item_class_has_corrupted_implicits,
    item_class_has_influences = h.conditions.item_class_has_influences,
    item_class_has_synthesised_implicits = h.conditions.item_class_has_synthesised_implicits,
    item_class_has_fractured_modifiers = h.conditions.item_class_has_fractured_modifiers,
}
}
-- Order matters!
-- Order matters!
-- Put most specific outcome at the top and the least specific at the bottom.
-- Put most specific outcome at the top and the least specific at the bottom.
c.automatic_upgraded_from = {
c.automatic_recipes = {
--[[
--[[
     {
     {
         defaults = {
         conditions = {
            arg_key = function (tpl_args, frame) end,
             function (tpl_args) end,
        },
        condition = {
             function (tpl_args, frame) end,
         },
         },
         text = '',
         text = '',
         groups = {
         parts = {
             {
             {
                 name = '',
                 name = '',
Line 176: Line 248:
         },
         },
     },
     },
    ]]
]]
    --
      
    -- 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.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.agate_amulet))
        end,
        groups = {
            {
                -- Lysah's Respite
                item_id = "Metadata/Items/DivinationCards/DivinationCardLysahsRespite",
                amount = 6,
            },
        },
    },
    {
        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.fmt.random, m_util.html.poe_color('unique', i18n.unset_ring))
        end,
        groups = {
            {
                -- The Penitent
                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.fmt.random, m_util.html.poe_color('unique', i18n.gold_ring))
        end,
        groups = {
            {
                -- Glimmer of Hope
                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.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.prismatic_ring))
        end,
        groups = {
            {
                -- Hope
                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.fmt.random, m_util.html.poe_color('rare', i18n.two_stone_ring))
        end,
        groups = {
            {
                -- Lantador's Lost Love
                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.fmt.random, m_util.html.poe_color('unique', i18n.two_stone_ring))
        end,
        groups = {
            {
                -- Heterochromia
                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.fmt.random, m_util.html.poe_color('unique', i18n.crystal_sceptre))
        end,
        groups = {
            {
                -- Light and Truth (Свет и правда)
                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.fmt.random, m_util.html.poe_color('unique', i18n.granite_flask))
        end,
        groups = {
            {
                -- Earth Drinker (Пьющий землю)
                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.fmt.random, m_util.html.poe_color('unique', i18n.nightmare_bascinet))
        end,
        groups = {
            {
                -- The Gladiator (Гладиатор)
                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.fmt.random, m_util.html.poe_color('unique', i18n.rustic_sash))
        end,
        groups = {
            {
                -- The Standoff (Противоборство)
                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.fmt.random, m_util.html.poe_color('unique', i18n.timeless_jewel))
        end,
        groups = {
            {
                -- Peaceful Moments (Безмятежные минуты)
                item_id = "Metadata/Items/DivinationCards/DivinationCardPeacefulMoments",
                amount = 5,
            },
        },
    },
    {
        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.fmt.random_two_implicit_corrupted, m_util.html.poe_color('unique', i18n.timeless_jewel))
        end,
        groups = {
            {
                -- The Eternal War (Вечная война)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheEternalWar",
                amount = 4,
            },
        },
    },
    {
        defaults = {
            is_drop_restricted = false,
        },
        condition = {
            h.conditions.factory.arg{arg='base_item_id', value='Metadata/Items/Currency/CurrencyItemisedProphecy'},
            function (tpl_args, frame)
                local patterns = {'Fated', 'AddsSpecificMod'}
                for _, p in ipairs(patterns) do
                    if string.find(tpl_args.prophecy_id, p, 1, true) then
                        return true
                    end
                end
                return false
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('prophecy', i18n.fated_unique_item_prophecy))
        end,
        groups = {
            {
                -- Akil's Prophecy
                item_id = "Metadata/Items/DivinationCards/DivinationCardAkilsProphecy",
                amount = 3,
            },
        },
    },
    --
    -- Item name
    --
    {
        defaults = {
            is_drop_restricted = false,
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='name', value=i18n.precursors_emblem},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.precursors_emblem))
        end,
        groups = {
            {
                -- Remembrance (Поминовение)
                item_id = "Metadata/Items/DivinationCards/DivinationCardRemembrance",
                amount = 8,
            },
        },
    },
    --
    -- Item name like
    --
    {
        defaults = {
            is_drop_restricted = false,
        },
        condition = {
            h.conditions.unique,
            function (tpl_args, frame)
                return string.find(tpl_args.name, i18n.atziri, 1, true)
            end,
            h.conditions.factory.arg{arg='is_fated', value=false}, -- Excludes Atziri's Reflection
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, i18n.atziri)))
        end,
        groups = {
            {
                -- The Admirer
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheAdmirer",
                amount = 9,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            function (tpl_args, frame)
                return string.find(tpl_args.name, i18n.doedre, 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, i18n.doedre)))
        end,
        groups = {
            {
                -- Doedre's Madness
                item_id = "Metadata/Items/DivinationCards/DivinationCardDoedresMadness",
                amount = 9,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            function (tpl_args, frame)
                return string.find(tpl_args.name, i18n.shavronne, 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, i18n.shavronne)))
        end,
        groups = {
            {
                -- The Aesthete
                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, i18n.rigwald, 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, i18n.rigwald)))
        end,
        groups = {
            {
                -- The Wolf
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheWolf",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            function (tpl_args, frame)
                return string.find(tpl_args.name, i18n.lioneye, 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, i18n.lioneye)))
        end,
        groups = {
            {
                -- The Lion
                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, i18n.farrul, 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, i18n.farrul)))
        end,
        groups = {
            {
                -- Council of Cats (Кошачий совет)
                item_id = "Metadata/Items/DivinationCards/DivinationCardCouncilOfCats",
                amount = 4,
            },
        },
    },
    {
        defaults = {
            is_drop_restricted = false,
        },
        condition = {
            h.conditions.unique,
            function (tpl_args, frame)
                return string.find(tpl_args.name, i18n.farrul, 1, true) or string.find(tpl_args.name, i18n.fenumus, 1, true) or string.find(tpl_args.name, i18n.saqawal, 1, true) or string.find(tpl_args.name, i18n.craiceann, 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, m_game.constants.leagues['Bestiary'].name)))
        end,
        groups = {
            {
                -- Boon of the First Ones (Дар Первых)
                item_id = 'Metadata/Items/DivinationCards/DivinationCardBoonOfTheFirstOnes',
                amount = 6,
            },
        },
    },
    --
    -- League-specific items
    --
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.league{league = m_game.constants.leagues['Nemesis'].name},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, m_game.constants.leagues['Nemesis'].name)))
        end,
        groups = {
            {
                -- The Valkyrie
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheValkyrie",
                amount = 8,
             
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.league{league = m_game.constants.leagues['Nemesis'].name},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, m_game.constants.leagues['Nemesis'].name)))
        end,
        groups = {
            {
                -- The Undaunted
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheUndaunted",
                amount = 5,
             
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.league{league = m_game.constants.leagues['Beyond'].name},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, m_game.constants.leagues['Beyond'].name)))
        end,
        groups = {
            {
                -- The Calling
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheCalling",
                amount = 6,
             
            },
        },
    },
    {
        defaults = {
            is_drop_restricted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.league{league = m_game.constants.leagues['Breach'].name},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, m_game.constants.leagues['Breach'].name)))
        end,
        groups = {
            {
                -- The Breach
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheBreach",
                amount = 4,
             
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.league{league = m_game.constants.leagues['Delve'].name},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, m_game.constants.leagues['Delve'].name)))
        end,
        groups = {
            {
                -- Alone in the Darkness
                item_id = "Metadata/Items/DivinationCards/DivinationCardAloneInTheDarkness",
                amount = 5,
             
            },
        },
    },
    {
        defaults = {
            is_drop_restricted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.league{league = m_game.constants.leagues['Metamorph'].name},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', string.format(i18n.fmt.x_item, m_game.constants.leagues['Metamorph'].name)))
        end,
        groups = {
            {
                -- Haunting Shadows (Преследующие тени)
                item_id = "Metadata/Items/DivinationCards/DivinationCardHauntingShadows",
                amount = 4,
            },
        },
    },
    {
        defaults = {
            is_drop_restricted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.league{league = m_game.constants.leagues['Abyss'].name},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('rare', string.format(i18n.fmt.x_item, m_game.constants.leagues['Abyss'].name)))
        end,
        groups = {
            {
                -- Abyssal Incubator (Инкубатор Бездны)
                item_id = "Metadata/Items/Currency/CurrencyIncubationAbyss",
                amount = 1,
            },
        },
    },
    --
    -- Subset of item class
    --
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Jewel'},
            function(tpl_args, frame)
                -- Get Primordial modifier from stats
                if tpl_args._stats.primordial_jewel_count then
                    return true
                end
                return false
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, string.format('%s %s', m_util.html.poe_color('mod', i18n.primordial), m_util.html.poe_color('unique', i18n.jewel)))
        end,
        groups = {
            {
                -- The Primordial (Первородный)
                item_id = "Metadata/Items/DivinationCards/DivinationCardThePrimordial",
                amount = 5,
            },
        },
    },
    {
        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.fmt.random_corrupted, m_util.html.poe_color('rare', i18n.tier_1_talisman))
        end,
        groups = {
            {
                -- Call to the First Ones
                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.fmt.random, i18n.talisman)
        end,
        groups = {
            {
                -- Primal Incubator
                item_id = "Metadata/Items/Currency/CurrencyIncubationTalismans",
                amount = 1,
            },
        },
    },
    {
        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.fmt.random, m_util.html.poe_color('normal', i18n.sacrifice_fragment))
        end,
        groups = {
            {
                -- Her Mask (Её маска)
                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.fmt.random, m_util.html.poe_color('normal', i18n.mortal_fragment))
        end,
        groups = {
            {
                -- Sambodhi's Vow (Клятва Самбодхи)
                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 tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'UberElderFragment', 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('normal', i18n.uber_elder_fragment))
        end,
        groups = {
            {
                -- The Eldritch Decay (Аномальное увядание)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheEldritchDecay",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='MapFragment'},
            function (tpl_args, frame)
                return tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'MapFragments', 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('normal', i18n.fragment))
        end,
        groups = {
            {
                -- Fragmented Incubator (Раздробленный инкубатор)
                item_id = "Metadata/Items/Currency/CurrencyIncubationFragments",
                amount = 1,
            },
        },
    },
    {
        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.fmt.random, m_util.html.poe_color('normal', i18n.breachstone))
        end,
        groups = {
            {
                -- The Obscured
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheObscured",
                amount = 7,
            },
        },
    },
    {
        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.fmt.random, m_util.html.poe_color('normal', i18n.pure_breachstone))
        end,
        groups = {
            {
                -- The Bargain (Сделка)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheBargain",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            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.fmt.random_x_amount, m_util.html.poe_color('currency', i18n.deafening_essence), 3)
        end,
        groups = {
            {
                -- The Cacophony
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheCacophony",
                amount = 8,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            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.fmt.random_x_amount, m_util.html.poe_color('currency', i18n.shrieking_essence), 9)
        end,
        groups = {
            {
                -- Harmony of Souls
                item_id = "Metadata/Items/DivinationCards/DivinationCardHarmonyOfSouls",
                amount = 9,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='is_essence', value=true},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_amount, m_util.html.poe_color('currency', i18n.essence), 3)
        end,
        groups = {
            {
                -- Three Voices
                item_id = "Metadata/Items/DivinationCards/DivinationCardThreeVoices",
                amount = 3,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='is_essence', value=true},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_amount, m_util.html.poe_color('currency', i18n.essence), 3)
        end,
        groups = {
            {
                -- Whispering Incubator (Шепчущий инкубатор)
                item_id = "Metadata/Items/Currency/CurrencyIncubationEssence",
                amount = 1,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Map'},
            h.conditions.factory.arg{arg='map_tier', value=5},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('normal', string.format(i18n.fmt.tier_x_map, 5)))
        end,
        groups = {
            {
                -- Cartographer's Delight
                item_id = "Metadata/Items/DivinationCards/DivinationCardCartographersDelight",
                amount = 3,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Map'},
            h.conditions.factory.arg{arg='map_tier', value=14},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('normal', string.format(i18n.fmt.tier_x_map, 14)))
        end,
        groups = {
            {
                -- The Surveyor
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheSurveyor",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Map'},
            h.conditions.factory.arg{arg='map_tier', value=15},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('normal', string.format(i18n.fmt.tier_x_map, 15)))
        end,
        groups = {
            {
                -- Lost Worlds
                item_id = "Metadata/Items/DivinationCards/DivinationCardLostWorlds",
                amount = 8,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Map'},
            h.conditions.factory.arg{arg='map_tier', value=15},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('rare', string.format(i18n.fmt.tier_x_map, 15)))
        end,
        groups = {
            {
                -- The Trial
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheTrial",
                amount = 7,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Map'},
            h.conditions.factory.arg{arg='map_tier', value=16},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_unidentified_corrupted, m_util.html.poe_color('rare', string.format(i18n.fmt.tier_x_map, 16)))
        end,
        groups = {
            {
                -- Left to Fate
                item_id = "Metadata/Items/DivinationCards/DivinationCardLeftToFate",
                amount = 4,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Map'},
            h.conditions.factory.arg{arg='map_tier', value=13},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, string.format(i18n.fmt.triskaidekaphobia, m_util.html.poe_color('rare', string.format(i18n.fmt.tier_x_map, 13))))
        end,
        groups = {
            {
                -- Triskaidekaphobia
                item_id = 'Metadata/Items/DivinationCards/DivinationCardTriskaidekaphobia',
                amount = 13,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Active Skill Gem'},
            h.conditions.factory.arg{arg='gem_tags', value=m_game.constants.item.gem_tags.golem.tag},
            h.conditions.factory.arg{arg='max_level', value=20},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('gem', string.format(i18n.fmt.level_x_y_gem, 21, m_game.constants.item.gem_tags.golem.tag)))
        end,
        groups = {
            {
                -- The Rite of Elements
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheRiteOfElements",
                amount = 5,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.gems.keys},
            h.conditions.factory.arg{arg='gem_tags', value=m_game.constants.item.gem_tags.trap.tag},
            h.conditions.factory.arg{arg='max_level', value=20},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('gem', string.format(i18n.fmt.superior_level_x_y_gem_q_z, 21, m_game.constants.item.gem_tags.trap.tag, 23)))
        end,
        groups = {
            {
                -- Deathly Designs (Смертельные замыслы)
                item_id = "Metadata/Items/DivinationCards/DivinationCardDeathlyDesigns",
                amount = 7,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Support Skill Gem'},
            h.conditions.factory.arg{arg='max_level', value=20},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('gem', string.format(i18n.fmt.level_x_y_gem, 20, m_game.constants.item.gem_tags.support.tag)))
        end,
        groups = {
            {
                -- Gift of the Gemling Queen
                item_id = "Metadata/Items/DivinationCards/DivinationCardGiftOfTheGemlingQueen",
                amount = 9,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Support Skill Gem'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('gem', string.format(i18n.fmt.superior_x_gem_q_y, m_game.constants.item.gem_tags.support.tag, 23)))
        end,
        groups = {
            {
                -- Dialla's Subjugation
                item_id = "Metadata/Items/DivinationCards/DivinationCardDiallasSubjugation",
                amount = 7,
            },
        },
    },
    {
        defaults = {
            is_drop_restricted = false,
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Support Skill Gem'},
            function (tpl_args, frame)
                return tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'Plus', 1, true)
            end,
            h.conditions.factory.arg{arg='max_level', value=5},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('gem', string.format(i18n.fmt.superior_level_x_support_plus_gem_q_y, 6, 20)))
        end,
        groups = {
            {
                -- The Cheater (Ловкач)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheCheater",
                amount = 3,
            },
        },
    },
    {
        defaults = {
            is_drop_restricted = false,
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Support Skill Gem'},
            function (tpl_args, frame)
                return tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'Plus', 1, true)
            end,
            h.conditions.factory.arg{arg='max_level', value=5},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('gem', string.format(i18n.fmt.superior_level_x_support_plus_gem_q_y, 6, 23)))
        end,
        groups = {
            {
                -- Desecrated Virtue (Осквернённая добродетель)
                item_id = "Metadata/Items/DivinationCards/DivinationCardDesecratedVirtue",
                amount = 9,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.gems.keys},
            h.conditions.factory.arg{arg='gem_tags', value=m_game.constants.item.gem_tags.chaos.tag},
            h.conditions.factory.arg{arg='max_level', value=20},
        },
        text = function(tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('gem', string.format(i18n.fmt.superior_level_x_y_gem_q_z, 21, m_game.constants.item.gem_tags.chaos.tag, 23)))
        end,
        groups = {
            {
                -- The Bitter Blossom (Колючее соцветие)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheBitterBlossom",
                amount = 3,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Active Skill Gem'},
            h.conditions.factory.arg{arg='gem_tags', value=m_game.constants.item.gem_tags.vaal.tag},
        },
        text = function(tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('gem', string.format(i18n.fmt.superior_x_gem_q_y, m_game.constants.item.gem_tags.vaal.tag, 20)))
        end,
        groups = {
            {
                -- Volatile Power
                item_id = "Metadata/Items/DivinationCards/DivinationCardVolatilePower",
                amount = 9,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.gems.keys},
            h.conditions.factory.arg{arg='max_level', value=20},
            function (tpl_args, frame)
                return tpl_args._flags.is_alt_quality_gem == true -- Flag is set in Module:Skill
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('gem', string.format(i18n.fmt.alt_level_x_gem_q_y, 19, 19)))
        end,
        groups = {
            {
                -- Dying Anguish (Смертельная тоска)
                item_id = "Metadata/Items/DivinationCards/DivinationCardDyingAnguish",
                amount = 8,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.gems.keys},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('gem', string.format(i18n.fmt.superior_gem)))
        end,
        groups = {
            {
                -- Gemcutter's Incubator (Инкубатор камнереза)
                item_id = "Metadata/Items/Currency/CurrencyIncubationGemLow",
                amount = 1,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='MapFragment'},
            function (tpl_args, frame)
                return tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'Scarab', 1, true)
            end,
            h.conditions.factory.arg{arg='tags', value='gilded_scarab'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('normal', i18n.gilded_scarab))
        end,
        groups = {
            {
                -- More is Never Enough
                item_id = "Metadata/Items/DivinationCards/DivinationCardMoreIsNeverEnough",
                amount = 7,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='MapFragment'},
            function (tpl_args, frame)
                return tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'Scarab', 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('normal', i18n.scarab))
        end,
        groups = {
            {
                -- Cameria's Cut
                item_id = "Metadata/Items/DivinationCards/DivinationCardCameriasCut",
                amount = 2,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='MapFragment'},
            function (tpl_args, frame)
                return tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'Scarab', 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('normal', i18n.scarab))
        end,
        groups = {
            {
                -- Skittering Incubator
                item_id = "Metadata/Items/Currency/CurrencyIncubationScarabs",
                amount = 1,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='StackableCurrency'},
            h.conditions.factory.arg{arg='tags', value='breachstone_splinter'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_amount, m_util.html.poe_color('currency', i18n.breachstone_splinter), 5)
        end,
        groups = {
            {
                -- The Puzzle (Головоломка)
                item_id = "Metadata/Items/DivinationCards/DivinationCardThePuzzle",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='StackableCurrency'},
            h.conditions.factory.arg{arg='tags', value='quality_currency'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_amount, m_util.html.poe_color('currency', i18n.quality_currency), 20)
        end,
        groups = {
            {
                -- The Master Artisan (Мастер-ремесленник)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheMasterArtisan",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='StackableCurrency'},
            function (tpl_args, frame)
                return tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'CurrencyDelve', 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_amount, m_util.html.poe_color('currency', i18n.fossil), 5)
        end,
        groups = {
            {
                -- The Tinkerer's Table (Верстак умельца)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheTinkerersTable",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='StackableCurrency'},
            function (tpl_args, frame)
                return tpl_args.metadata_id and string.find(tpl_args.metadata_id, 'CurrencyDelve', 1, true)
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('currency', i18n.fossil))
        end,
        groups = {
            {
                -- Fossilised Incubator (Ископаемый инкубатор)
                item_id = "Metadata/Items/Currency/CurrencyIncubationFossils",
                amount = 1,
            },
        },
    },
    --
    -- Single item class
    --
    {
        defaults = {
            is_drop_restricted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='UniqueFragment'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.harbinger_fragment))
        end,
        groups = {
            {
                -- The Messenger
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheMessenger",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Body Armour'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.body_armour))
        end,
        groups = {
            {
                -- The Body
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheBody",
                amount = 4,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Shield'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.shield))
        end,
        groups = {
            {
                -- The Mercenary
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheMercenary",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Helmet'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_two_veiled, m_util.html.poe_color('rare', i18n.helmet))
        end,
        groups = {
            {
                -- The Journalist (Журналист)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheJournalist",
                amount = 10,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Helmet'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_eternal_labyrinth_enchantment, m_util.html.poe_color('unique', i18n.helmet))
        end,
        groups = {
            {
                -- Divine Justice (Божественное правосудие)
                item_id = "Metadata/Items/DivinationCards/DivinationCardDivineJustice",
                amount = 1,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Claw'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.claw))
        end,
        groups = {
            {
                -- The Wolverine
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheWolverine",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', values={'Dagger', 'Rune Dagger'}},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.dagger))
        end,
        groups = {
            {
                -- Assassin's Favour
                item_id = "Metadata/Items/DivinationCards/DivinationCardAssassinsFavour",
                amount = 9,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Sceptre'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_shaper, m_util.html.poe_color('magic', string.format('%s %s', i18n.sceptre, h.modifier_link{id='LocalIncreasedAttackSpeed8'})))
        end,
        groups = {
            {
                -- The Lord of Celebration
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheLordOfCelebration",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Bow'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.bow))
        end,
        groups = {
            {
                -- Hunter's Resolve
                item_id = "Metadata/Items/DivinationCards/DivinationCardHuntersResolve",
                amount = 8,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Jewel'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.jewel))
        end,
        groups = {
            {
                -- The Garish Power
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheGarishPower",
                amount = 4,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Jewel'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.jewel))
        end,
        groups = {
            {
                -- The Eye of the Dragon
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheEyeOfTheDragon",
                amount = 10,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Jewel'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('magic', string.format('%s %s', h.modifier_link{id='PercentIncreasedLifeJewel'}, i18n.jewel)))
        end,
        groups = {
            {
                -- Shard of Fate (Осколок рока)
                item_id = "Metadata/Items/DivinationCards/DivinationCardShardOfFate",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Jewel'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('magic', string.format('%s %s', i18n.jewel, h.modifier_link{id='CriticalStrikeMultiplierJewel'})))
        end,
        groups = {
            {
                -- The Mountain (Гора)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheMountain",
                amount = 6,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Amulet'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('magic', string.format('%s %s', h.modifier_link{id='IncreasedEnergyShieldPercent7'}, i18n.amulet)))
        end,
        groups = {
            {
                -- The Sigil
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheSigil",
                amount = 3,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Amulet'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('rare', i18n.amulet))
        end,
        groups = {
            {
                -- The Warden
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheWarden",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Ring'},
            function (tpl_args, frame)
                return tpl_args.drop_level <= 83
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_item_level_x, 83, m_util.html.poe_color('magic', string.format('%s %s', i18n.ring, h.modifier_link{id='ChaosResist6'})))
        end,
        groups = {
            {
                -- The Lord in Black
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheLordInBlack",
                amount = 6,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Ring'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_item_level_x, 100, m_util.html.poe_color('rare', i18n.ring))
        end,
        groups = {
            {
                -- The Opulent
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheOpulecent",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Ring'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.ring))
        end,
        groups = {
            {
                -- Hubris
                item_id = "Metadata/Items/DivinationCards/DivinationCardHubris",
                amount = 5,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Ring'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.ring))
        end,
        groups = {
            {
                -- Blind Venture
                item_id = "Metadata/Items/DivinationCards/DivinationCardBlindVenture",
                amount = 7,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Belt'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.belt))
        end,
        groups = {
            {
                -- The Wretched
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheWretched",
                amount = 6,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Map'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.map))
        end,
        groups = {
            {
                -- The Encroaching Darkness
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheEncroachingDarkness",
                amount = 8,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Map'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.map))
        end,
        groups = {
            {
                -- Otherworldly Incubator
                item_id = "Metadata/Items/Currency/CurrencyIncubationUniqueMaps",
                amount = 8,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Wand'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.wand))
        end,
        groups = {
            {
                -- The Traitor
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheTraitor",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', values={'Staff', 'Warstaff'}},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.staff))
        end,
        groups = {
            {
                -- The Tower
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheTower",
                amount = 6,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values={'Staff', 'Warstaff'}},
            h.conditions.factory.arg{arg='tags', value='small_staff', negate=true}, -- Small staves excluded because they don't have enough sockets
            function (tpl_args, frame)
                return tpl_args.drop_level <= 66
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_link_item_level_y, 5, 66, m_util.html.poe_color('normal', i18n.staff))
        end,
        groups = {
            {
                -- The Flora's Gift
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheFlorasGift",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values={'Staff', 'Warstaff'}},
            h.conditions.factory.arg{arg='tags', value='small_staff', negate=true}, -- Small staves excluded because they don't have enough sockets
            function (tpl_args, frame)
                return tpl_args.drop_level <= 55
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_link_item_level_y, 6, 55, m_util.html.poe_color('normal', i18n.staff))
        end,
        groups = {
            {
                -- The Dark Mage
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheDarkMage",
                amount = 6,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Body Armour'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_link_item_level_y, 6, 100, m_util.html.poe_color('normal', i18n.body_armour))
        end,
        groups = {
            {
                -- The Dapper Prodigy
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheDapperProdigy",
                amount = 6,
            },
        },
    },
        {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Body Armour'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_link_item_level_y_random_influenced, 6, 100, m_util.html.poe_color('normal', i18n.body_armour))
        end,
        groups = {
            {
                -- Draped in Dreams (Облачённый в мечты)
                item_id = "Metadata/Items/DivinationCards/DivinationCardDrapedInDreams",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Body Armour'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_link, 6, m_util.html.poe_color('normal', i18n.body_armour))
        end,
        groups = {
            {
                -- The Chains that Bind
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheChainsThatBind",
                amount = 11,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Body Armour'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_x_link, 6, m_util.html.poe_color('normal', i18n.body_armour))
        end,
        groups = {
            {
                -- Geomancer's Incubator (Инкубатор геоманта)
                item_id = "Metadata/Items/Currency/CurrencyIncubationArmour6Linked",
                amount = 1,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Body Armour'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_item_level_x, 100, m_util.html.poe_color('rare', i18n.body_armour))
        end,
        groups = {
            {
                -- Destined to Crumble
                item_id = "Metadata/Items/DivinationCards/DivinationCardDestinedToCrumble",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Map'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('normal', i18n.map))
        end,
        groups = {
            {
                -- Boundless Realms
                item_id = "Metadata/Items/DivinationCards/DivinationCardBoundlessRealms",
                amount = 4,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Map'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('rare', i18n.map))
        end,
        groups = {
            {
                -- The Explorer
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheExplorer",
                amount = 6,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', value='Gloves'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.gloves))
        end,
        groups = {
            {
                -- Mitts
                item_id = "Metadata/Items/DivinationCards/DivinationCardMitts",
                amount = 5,
            },
        },
    },
    {
        defaults = {
            is_drop_restricted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='DivinationCard'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('divination', i18n.divination_card))
        end,
        groups = {
            {
                -- The Gambler
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheGambler",
                amount = 5,
            },
        },
    },
    {
        defaults = {
            is_drop_restricted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='DivinationCard'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('divination', i18n.divination_card))
        end,
        groups = {
            {
                -- Stacked Deck
                item_id = "Metadata/Items/DivinationCards/DivinationCardDeck",
                amount = 1,
            },
        },
    },
    --
    -- Multiple item classes
    --
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', values={'One Hand Axe', 'Two Hand Axe'}},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.axe))
        end,
        groups = {
            {
                -- The Battle Born
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheBattleBorn",
                amount = 5,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', value='Active Skill Gem'},
            h.conditions.factory.arg{arg='gem_tags', value=m_game.constants.item.gem_tags.aura.tag},
            h.conditions.factory.arg{arg='max_level', value=20},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('gem', string.format(i18n.fmt.level_x_y_gem, 21, m_game.constants.item.gem_tags.aura.tag)))
        end,
        groups = {
            {
                -- The Wilted Rose
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheWiltedRose",
                amount = 7,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.gems.keys},
            h.conditions.factory.arg{arg='max_level', value=20},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('gem', string.format(i18n.fmt.level_x_gem, 20)))
        end,
        groups = {
            {
                -- The Fox
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheFox",
                amount = 6,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.gems.keys},
            h.conditions.factory.arg{arg='gem_tags', value=m_game.constants.item.gem_tags.minion.tag},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('gem', string.format(i18n.fmt.superior_x_gem_q_y, m_game.constants.item.gem_tags.minion.tag, 20)))
        end,
        groups = {
            {
                -- The Summoner
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheSummoner",
                amount = 6,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.gems.keys},
            -- since this is checking against an array, it will check if it contains the "Spell" value
            h.conditions.factory.arg{arg='gem_tags', value=m_game.constants.item.gem_tags.spell.tag},
            h.conditions.factory.arg{arg='max_level', value=20},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('gem', string.format(i18n.fmt.level_x_y_gem, 21, m_game.constants.item.gem_tags.spell.tag)))
        end,
        groups = {
            {
                --The Cataclysm
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheCataclysm",
                amount = 13,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.gems.keys},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('gem', string.format(i18n.fmt.superior_gem_q_x, 20)))
        end,
        groups = {
            {
                -- Gemcutter's Promise
                item_id = "Metadata/Items/DivinationCards/DivinationCardGemcuttersPromise",
                amount = 3,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.armor.keys},
            h.conditions.factory.arg{arg='tags', value='int_armour'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('magic', string.format('%s %s', h.modifier_link{id='LocalIncreasedEnergyShieldPercentAndStunRecovery6'}, i18n.armour)))
        end,
        groups = {
            {
                -- The Inoculated
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheInoculated",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.armor.keys},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('magic', string.format('%s %s', i18n.life, i18n.armour)))
        end,
        groups = {
            {
                -- The Carrion Crow
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheCarrionCrow",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.jewellery.keys},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_shaper_item_level_x, 100, m_util.html.poe_color('rare', i18n.jewellery))
        end,
        groups = {
            {
                -- Perfection
                item_id = "Metadata/Items/DivinationCards/DivinationCardPerfection",
                amount = 5,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.jewellery.keys},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.jewellery))
        end,
        groups = {
            {
                -- The Cache (Тайник)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheCache",
                amount = 6,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.jewellery.keys},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_two_influenced_item_level_x, 86, m_util.html.poe_color('rare', i18n.jewellery))
        end,
        groups = {
            {
                -- The Awakened (Пробуждённые)
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheAwakened",
                amount = 6,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.jewellery.keys},
            function (tpl_args, frame)
                return tpl_args.drop_level <= 79
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_item_level_x, 79, m_util.html.poe_color('rare', i18n.jewellery))
        end,
        groups = {
            {
                -- The Lover
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheLover",
                amount = 2,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values={'Ring', 'Amulet'}},
            function (tpl_args, frame)
                return tpl_args.drop_level <= 85
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_item_level_x, 85, m_util.html.poe_color('magic', string.format('%s %s', h.modifier_link{id='ReduceGlobalFlatManaCostStrIntMasterVendor'}, i18n.jewellery)))
        end,
        groups = {
            {
                -- Blessing of God
                item_id = "Metadata/Items/DivinationCards/DivinationCardBlessingOfGod",
                amount = 3,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values={'Ring', 'Amulet'}},
            function (tpl_args, frame)
                return tpl_args.drop_level <= 76
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_item_level_x, 76, m_util.html.poe_color('magic', string.format('%s %s', h.modifier_link{id='AddedLightningDamage9'}, i18n.jewellery)))
        end,
        groups = {
            {
                --Struck by Lightning
                item_id = "Metadata/Items/DivinationCards/DivinationCardStruckByLightning",
                amount = 3,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.jewellery.keys},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_shaper_hunter_item_level_x, 100, m_util.html.poe_color('magic', string.format('%s %s', i18n.jewellery, h.modifier_link{id='GrantsCatAspectCrafted'})))
        end,
        groups = {
            {
                -- A Familiar Call (Знакомый зов)
                item_id = "Metadata/Items/DivinationCards/DivinationCardAFamiliarCall",
                amount = 3,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values={'Two Hand Sword', 'Two Hand Axe', 'Two Hand Mace', 'Staff', 'Bow', 'Warstaff'}},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_item_level_x, 100, m_util.html.poe_color('magic', string.format('%s %s', h.modifier_link{id='LocalIncreasedPhysicalDamagePercent8'}, i18n.two_hand_weapon)))
        end,
        groups = {
            {
                -- Merciless Armament
                item_id = "Metadata/Items/DivinationCards/DivinationCardMercilessArmament",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values={'Thrusting One Hand Sword', 'One Hand Sword', 'One Hand Axe', 'One Hand Mace', 'Sceptre', 'Dagger', 'Claw', 'Wand', 'Rune Dagger'}},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_two_influenced_item_level_x, 100, m_util.html.poe_color('rare', i18n.one_hand_weapon))
        end,
        groups = {
            {
                -- Prometheus' Armoury (Кузня Прометея)
                item_id = "Metadata/Items/DivinationCards/DivinationCardPrometheusArmoury",
                amount = 6,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values={'Thrusting One Hand Sword', 'One Hand Sword', 'One Hand Axe', 'One Hand Mace', 'Sceptre', 'Dagger', 'Claw', 'Wand', 'Rune Dagger'}},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_item_level_x, 100, m_util.html.poe_color('magic', string.format('%s %s', h.modifier_link{id='LocalIncreasedPhysicalDamagePercent8'}, i18n.one_hand_weapon)))
        end,
        groups = {
            {
                -- The Jester
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheJester",
                amount = 9,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values={'Sceptre', 'Wand', 'Rune Dagger'}},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_item_level_x, 100, m_util.html.poe_color('magic', string.format('%s %s', h.modifier_link{id='SpellDamageOnWeapon8_'}, i18n.one_hand_weapon)))
        end,
        groups = {
            {
                -- The Road to Power
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheRoadToPower",
                amount = 7,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', values={'Thrusting One Hand Sword', 'One Hand Sword', 'Two Hand Sword'}},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.sword))
        end,
        groups = {
            {
                -- The Gentleman
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheGentleman",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.weapons.keys},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_item_level_x, 100, m_util.html.poe_color('magic',string.format('%s %s', h.modifier_link{id='LocalIncreasedPhysicalDamagePercent8'}, i18n.weapon)))
        end,
        groups = {
            {
                -- The Tyrant
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheTyrant",
                amount = 9,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.weapons.keys},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('magic', string.format('%s %s', i18n.weapon, h.modifier_link{id='StrIntMasterItemGenerationCanHaveMultipleCraftedMods'})))
        end,
        groups = {
            {
                -- The Web
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheWeb",
                amount = 8,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.factory.arg{arg='class_id', values_assoc=cfg.class_groups.weapons.keys},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_corrupted, m_util.html.poe_color('unique', i18n.weapon))
        end,
        groups = {
            {
                -- Atziri's Arsenal
                item_id = "Metadata/Items/DivinationCards/DivinationCardAtzirisArsenal",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            -- specifically exclude crit flasks
            h.conditions.factory.arg{arg='class_id', values={'LifeFlask', 'ManaFlask', 'HybridFlask', 'UtilityFlask'}},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('magic', string.format('%s %s', h.modifier_link{id='FlaskChanceRechargeOnCrit1'}, i18n.flask)))
        end,
        groups = {
            {
                -- The Surgeon
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheSurgeon",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.item_class_has_corrupted_implicits,
            h.conditions.item_class_has_influences,
            function (tpl_args, frame)
                return tpl_args.drop_leagues ~= nil and #tpl_args.drop_leagues ~= 0
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_two_implicit_corrupted_two_influenced_item_level_x, 97, m_util.html.poe_color('unique', i18n.league_specific_item))
        end,
        groups = {
            {
                -- Fateful Meeting (Судьбоносная встреча)
                item_id = "Metadata/Items/DivinationCards/DivinationCardFatefulMeeting",
                amount = 9,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            h.conditions.item_class_has_influences,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_influenced, m_util.html.poe_color('unique', i18n.item))
        end,
        groups = {
            {
                -- Prejudice (Предрассудки)
                item_id = "Metadata/Items/DivinationCards/DivinationCardPrejudice",
                amount = 7,
            },
        },
    },
    {
        defaults = {
            is_corrupted = false,
        },
        condition = {
            h.conditions.unique,
            h.conditions.item_class_has_corrupted_implicits,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_two_implicit_corrupted, m_util.html.poe_color('unique', i18n.item))
        end,
        groups = {
            {
                -- Arrogance of the Vaal
                item_id = "Metadata/Items/DivinationCards/DivinationCardArroganceOfTheVaal",
                amount = 8,
            },
        },
    },
    --
    -- Generic items
    --
    {
        condition = {
            h.conditions.unique,
            function (tpl_args, frame)
                return tpl_args.drop_leagues ~= nil and #tpl_args.drop_leagues ~= 0
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.league_specific_item))
        end,
        groups = {
            {
                -- Time-Lost Relic
                item_id = "Metadata/Items/DivinationCards/DivinationCardTimeLostRelic",
                amount = 10,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
            function (tpl_args, frame)
                return tpl_args.drop_leagues ~= nil and #tpl_args.drop_leagues ~= 0
            end,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.league_specific_item))
        end,
        groups = {
            {
                -- Time-Lost Incubator (Затерянный во времени инкубатор)
                item_id = "Metadata/Items/Currency/CurrencyIncubationUniqueLeague",
                amount = 1,
            },
        },
    },
    {
        condition = {
            h.conditions.normal,
            -- should exclude all items that can't be rare
            h.conditions.factory.arg{arg='drop_rarities_ids', value='rare'},
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random_elder_item_level_x, 100, m_util.html.poe_color('rare', i18n.item))
        end,
        groups = {
            {
                -- The Hale Heart
                item_id = "Metadata/Items/DivinationCards/DivinationCardTheHaleHeart",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.item))
        end,
        groups = {
            {
                -- Jack in the Box
                item_id = "Metadata/Items/DivinationCards/DivinationCardJackInTheBox",
                amount = 4,
            },
        },
    },
    {
        condition = {
            h.conditions.unique,
        },
        text = function (tpl_args, frame)
            return string.format(i18n.fmt.random, m_util.html.poe_color('unique', i18n.item))
        end,
        groups = {
            {
                -- Singular Incubator (Необычный инкубатор)
                item_id = "Metadata/Items/Currency/CurrencyIncubationUniques",
                amount = 1,
            },
        },
    },
     -- TODO: The void?
}
}


Line 2,586: Line 258:
local p = {}
local p = {}


function p.process_upgraded_from(tpl_args, frame)
function p.process_recipes(tpl_args)
     local query_data = {
     local query_data = {
         id = {},
         id = {},
Line 2,592: Line 264:
         page = {},
         page = {},
     }
     }
     local sets = {}
     local recipes = {}
      
      
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
     -- Manual data
     -- Manual data
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
     local setid = #sets + 1
     local recipe_num = #recipes + 1
     local set
     local recipe
     repeat
     repeat
         local prefix = string.format('upgraded_from_set%s_', setid)
         local prefix = string.format('recipe%s_', recipe_num)
         local groupid = 1
         local part_num = 1
         local group
         local part
         set = {
         recipe = {
             groups = {},
             parts = {},
             text = m_util.cast.text(tpl_args[prefix .. 'text']),
            result_amount = tonumber(tpl_args[prefix .. 'result_amount']) or 1,
             text = m_util.cast.text(tpl_args[prefix .. 'description']),
             automatic = false,
             automatic = false,
         }
         }
         repeat  
         repeat  
             local group_prefix = string.format('%sgroup%s_', prefix, groupid)
             local part_prefix = string.format('%spart%s_', prefix, part_num)
             group = {
             part = {
                 item_name = tpl_args[group_prefix .. 'item_name'],
                 item_name = tpl_args[part_prefix .. 'item_name'],
                 item_id = tpl_args[group_prefix .. 'item_id'],  
                 item_id = tpl_args[part_prefix .. 'item_id'],  
                 item_page = tpl_args[group_prefix .. 'item_page'],  
                 item_page = tpl_args[part_prefix .. 'item_page'],  
                 amount = tonumber(tpl_args[group_prefix .. 'amount']),
                 amount = tonumber(tpl_args[part_prefix .. 'amount']),
                 notes = m_util.cast.text(tpl_args[group_prefix .. 'notes']),
                 notes = m_util.cast.text(tpl_args[part_prefix .. 'notes']),
             }
             }
              
              
             if group.item_name ~= nil or group.item_id ~= nil or group.item_page ~= nil then
             if part.item_name ~= nil or part.item_id ~= nil or part.item_page ~= nil then
                 if group.amount == nil then
                 if part.amount == nil then
                     error(string.format(i18n.errors.missing_amount, group_prefix .. 'amount'))
                     error(string.format(i18n.errors.missing_amount, part_prefix .. 'amount'))
                 else
                 else
                     for key, array in pairs(query_data) do
                     for key, array in pairs(query_data) do
                         local value = group['item_' .. key]
                         local value = part['item_' .. key]
                         if value then
                         if value then
                             if array[value] then
                             if array[value] then
                                 table.insert(array[value], {setid, groupid})
                                 table.insert(array[value], {recipe_num, part_num})
                             else
                             else
                                 array[value] = {{setid, groupid}, }
                                 array[value] = {{recipe_num, part_num}, }
                             end
                             end
                         end
                         end
                     end
                     end
                     set.groups[#set.groups+1] = group
                     recipe.parts[#recipe.parts+1] = part
                 end
                 end
             end
             end
              
              
             groupid = groupid + 1
             part_num = part_num + 1
         until group.item_name == nil and group.item_id == nil and group.item_page == nil
         until part.item_name == nil and part.item_id == nil and part.item_page == nil
          
          
         -- set was empty, can terminate safely
         -- recipe was empty, can terminate safely
         if #set.groups == 0 then
         if #recipe.parts == 0 then
             set = nil
             recipe = nil
         else
         else
             setid = setid + 1
             recipe_num = recipe_num + 1
             sets[#sets+1] = set
             recipes[#recipes+1] = recipe
         end
         end
     until set == nil
     until recipe == nil
 
     -- ------------------------------------------------------------------------
     -- ------------------------------------------------------------------------
     -- Automatic
     -- Automatic
Line 2,654: Line 328:
     --  maps
     --  maps
     --
     --
     local automatic_index = #sets + 1
     local automatic_index = #recipes + 1
     -- TODO: 3.9.0 Unsure how this works yet, so disabled for now
     -- 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
     --[[if tpl_args.atlas_connections and tpl_args.rarity_id == 'normal' then
         local results = m_cargo.query(
         local results = m_cargo.query(
             {'items', 'maps'},
             {'items', 'maps'},
Line 2,666: Line 340:
         )
         )
         for _, row in ipairs(results) do
         for _, row in ipairs(results) do
             sets[#sets+1] = {
             recipes[#recipes+1] = {
                 text = i18n.misc.upgraded_from_map,
                 text = i18n.misc.upgraded_from_map,
                 groups = {
                 result_amount = 1,
                parts = {
                     {
                     {
                         item_name = row['items.name'],
                         item_name = row['items.name'],
Line 2,694: Line 369:
         )
         )
         for _, row in ipairs(results) do
         for _, row in ipairs(results) do
             sets[#sets+1] = {
             recipes[#recipes+1] = {
                 text = nil,
                 text = nil,
                 groups = {
                 result_amount = 1,
                parts = {
                     {
                     {
                         item_name = row['items.name'],
                         item_name = row['items.name'],
Line 2,715: Line 391:
      
      
     -- exclude remnant of corruption via type
     -- exclude remnant of corruption via type
     if tpl_args.is_essence and tpl_args.essence_type > 0 then  
     if tpl_args._flags.is_essence and tpl_args.essence_type > 0 then  
         local results = m_cargo.query(
         local results = m_cargo.query(
             {'items', 'essences'},
             {'items', 'essences'},
Line 2,750: Line 426:
             if row['essences.category'] == tpl_args.essence_category then
             if row['essences.category'] == tpl_args.essence_category then
                 -- 3 to 1 recipe
                 -- 3 to 1 recipe
                 sets[#sets+1] = {
                 recipes[#recipes+1] = {
                     automatic = true,
                     automatic = true,
                    result_amount = 1,
                     text = nil,
                     text = nil,
                     groups = {
                     parts = {
                         {
                         {
                             item_id = row['items.metadata_id'],
                             item_id = row['items.metadata_id'],
Line 2,763: Line 440:
                 }
                 }
                 -- corruption +1
                 -- corruption +1
                 sets[#sets+1] = {
                 recipes[#recipes+1] = {
                     automatic = true,
                     automatic = true,
                    result_amount = 1,
                     text = i18n.essence_plus_one_level,
                     text = i18n.essence_plus_one_level,
                     groups = {
                     parts = {
                         {
                         {
                             item_id = row['items.metadata_id'],
                             item_id = row['items.metadata_id'],
Line 2,783: Line 461:
             elseif tonumber(row['essences.type']) == tpl_args.essence_type - 1 then
             elseif tonumber(row['essences.type']) == tpl_args.essence_type - 1 then
                 -- corruption type change
                 -- corruption type change
                 sets[#sets+1] = {
                 recipes[#recipes+1] = {
                     automatic = true,
                     automatic = true,
                    result_amount = 1,
                     text = i18n.essence_type_change,
                     text = i18n.essence_type_change,
                     groups = {
                     parts = {
                         {
                         {
                             item_id = row['items.metadata_id'],
                             item_id = row['items.metadata_id'],
Line 2,806: Line 485:
      
      
     -- data based on mapping
     -- data based on mapping
     if tpl_args.drop_enabled and not tpl_args.upgraded_from_disabled then
     if tpl_args.drop_enabled and not tpl_args.disable_automatic_recipes then
         for _, data in ipairs(c.automatic_upgraded_from) do
        -- Test and cache results of all named conditions
             data.defaults = data.defaults or {}
         for k, condition in pairs(c.named_conditions) do
             local continue = true
             if type(condition) == 'function' then
            for key, value in pairs(c.automatic_upgraded_from_defaults) do
                c.named_conditions[k] = condition(tpl_args)
                local func
             end
                local v = data.defaults[key]
        end
                if v == false then
 
                    -- check is disabled specifically, continue
        for _, data in ipairs(c.automatic_recipes) do
                elseif v == nil then
            local valid = true -- Can this recipe produce the item?
                    func = value
 
                elseif type(v) == 'function' then
            -- Check cached results for named conditions
                    func = v
            for k, condition in pairs(c.named_conditions) do
                else
                 if data.conditions[k] then
                    error(string.format('Invalid value for defaults at data %s', mw.dumpObject(data)))
                     valid = condition
                end
                     if not valid then
                 if func then
                     continue = func(tpl_args, frame) and continue
                     if not continue then
                         break
                         break
                     end  
                     end
                 end
                 end
             end
             end
             for _, condition in ipairs(data.condition) do
 
                 continue = condition(tpl_args, frame) and continue
            -- Test anonymous conditions
                 if not continue then
             for _, condition in ipairs(data.conditions) do
                 valid = condition(tpl_args) and valid
                 if not valid then
                     break
                     break
                 end
                 end
             end
             end
           
 
             if continue then
             if valid then
                 sets[#sets+1] = {
                 recipes[#recipes+1] = {
                     automatic = true,
                     automatic = true,
                     text = data.text(tpl_args, frame),
                    result_amount = 1,
                     groups = data.groups,
                     text = data.text(),
                     parts = data.parts,
                 }
                 }
                 for groupid, row in ipairs(data.groups) do
                 for part_num, row in ipairs(data.parts) do
                     if query_data['id'][row.item_id] then
                     if query_data['id'][row.item_id] then
                         table.insert(query_data['id'][row.item_id], {#sets, groupid})
                         table.insert(query_data['id'][row.item_id], {#recipes, part_num})
                     else
                     else
                         query_data['id'][row.item_id] = {{#sets, groupid}, }
                         query_data['id'][row.item_id] = {{#recipes, part_num}, }
                     end
                     end
                 end
                 end
Line 2,853: Line 532:
     end
     end
      
      
     if #sets == 0 then
     if #recipes == 0 then
         return
         return
     end
     end
     --
     --
     -- Fetch item data in a single query to sacrifice database load with a lot of upgraded_from references
     -- Fetch item data in a single query to sacrifice database load with a lot of references
     --
     --
     local query_data_array = {
     local query_data_array = {
Line 2,888: Line 567:
         }
         }
     )
     )
      
 
     -- Now do The Void
    for _, row in ipairs(results) do
        if row[query_fields.id] and string.find(row[query_fields.id], 'Metadata/Items/DivinationCards/', 1, true) then
            local part = {
                item_id = 'Metadata/Items/DivinationCards/DivinationCardTheVoid',
                amount = 1,
            }
            local result = m_cargo.query(
                {'items'},
                {'items._pageName',  'items.name', 'items.metadata_id'},
                {
                    where=string.format('%s = "%s"', query_fields.id, part.item_id),
                }
            )
            if #result > 0 then
                recipes[#recipes+1] = {
                    automatic = true,
                    result_amount = 1,
                    text = i18n.the_void,
                    parts = {part},
                }
                if query_data['id'][part.item_id] then
                    table.insert(query_data['id'][part.item_id], {#recipes, 1})
                else
                    query_data['id'][part.item_id] = {{#recipes, 1}, }
                end
                table.insert(results, result[1])
            end
            break
        end
    end
 
     for _, row in ipairs(results) do
     for _, row in ipairs(results) do
         for key, thing_array in pairs(query_data) do
         for key, thing_array in pairs(query_data) do
             local set_groups = thing_array[row[query_fields[key]]]
             local recipe_parts = thing_array[row[query_fields[key]]]
             if set_groups then
             if recipe_parts then
                 for _, set_group in ipairs(set_groups) do
                 for _, recipe_part in ipairs(recipe_parts) do
                     local entry = sets[set_group[1]].groups[set_group[2]]
                     local entry = recipes[recipe_part[1]].parts[recipe_part[2]]
                     for entry_key, data_key in pairs(query_fields) do
                     for entry_key, data_key in pairs(query_fields) do
                         -- metadata_id may be nil, since we don't know them for unique items
                         -- metadata_id may be nil, since we don't know them for unique items
Line 2,912: Line 623:
         -- query data was pruned of existing keys earlier, so only broken keys remain
         -- query data was pruned of existing keys earlier, so only broken keys remain
         for key, array in pairs(query_data) do
         for key, array in pairs(query_data) do
             for thing, set_groups in pairs(array) do
             for thing, recipe_parts in pairs(array) do
                 for _, set_group in ipairs(set_groups) do
                 for _, recipe_part in ipairs(recipe_parts) do
                     tpl_args._flags.broken_upgraded_from_reference = true
                     tpl_args._flags.invalid_recipe_parts = 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)
                     tpl_args._errors[#tpl_args._errors+1] = m_util.string.format(i18n.errors.invalid_recipe_parts, string.format('recipe%s_part%s_item_%s', recipe_part[1], recipe_part[2], key), thing)
                 end
                 end
             end
             end
Line 2,924: Line 635:
     -- Check for duplicates
     -- Check for duplicates
     --
     --
     local delete_sets = {}
     local delete_recipes = {}
     for i=automatic_index, #sets do
     for i=automatic_index, #recipes do
         for j=1, automatic_index-1 do
         for j=1, automatic_index-1 do
             if #sets[i].groups == #sets[j].groups then
             if #recipes[i].parts == #recipes[j].parts then
                 local match = true
                 local match = true
                 for row_id, row in ipairs(sets[i].groups) do
                 for row_id, row in ipairs(recipes[i].parts) 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.
                     -- 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
                     for _, key in ipairs({'item_id', 'item_name', 'item_page'})  do
                         match = match and (row[key] == sets[j].groups[row_id][key])
                         match = match and (row[key] == recipes[j].parts[row_id][key])
                     end
                     end
                 end
                 end
                 if match then
                 if match then
                     tpl_args._flags.duplicate_upgraded_from_reference = true
                     tpl_args._flags.duplicate_recipes = true
                     tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.upgraded_from_duplicate, j)
                     tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.duplicate_recipes, j)
                     delete_sets[#delete_sets+1] = j  
                     delete_recipes[#delete_recipes+1] = j  
                 end
                 end
             end
             end
Line 2,944: Line 655:
     end
     end
      
      
     for offset, index in ipairs(delete_sets) do
     for offset, index in ipairs(delete_recipes) do
         table.remove(sets, index-(offset-1))
         table.remove(recipes, index-(offset-1))
     end
     end
     --
     --
     -- Set data
     -- Set data
     --  
     --  
     tpl_args.upgrade_from_sets = sets
     tpl_args.recipes = recipes
      
      
     -- set upgraded_from data
     -- Set recipes data
     for i, set in ipairs(sets) do
     for i, recipe in ipairs(recipes) do
         tpl_args._subobjects[#tpl_args._subobjects+1] = {
         table.insert(tpl_args._store_data, {
             _table = 'upgraded_from_sets',
             _table = 'acquisition_recipes',
             set_id = i,
             recipe_id = i,
             text = set.text,
             result_amount = recipe.result_amount,
             automatic = set.automatic,
            description = recipe.text,
         }
             automatic = recipe.automatic,
       
         })
         for j, group in ipairs(set.groups) do
         for j, part in ipairs(recipe.parts) do
             tpl_args._subobjects[#tpl_args._subobjects+1] = {
             table.insert(tpl_args._store_data, {
                 _table = 'upgraded_from_groups',
                 _table = 'acquisition_recipe_parts',
                 group_id = j,
                 part_id = j,
                 set_id = i,
                 recipe_id = i,
                 item_name = group.item_name,
                 item_name = part.item_name,
                 item_id = group.item_id,
                 item_id = part.item_id,
                 item_page = group.item_page,
                 item_page = part.item_page,
                 amount = group.amount,
                 amount = part.amount,
                 notes = group.notes,
                 notes = part.notes,
             }
             })
         end
         end
     end
     end
Line 2,980: Line 692:
--
--


function p.debug_validate_auto_upgraded_from(frame)
function p.debug_validate_auto_upgraded_from()
    frame = m_util.misc.get_frame(frame)
   
     local q = {}
     local q = {}
     local chk = {}
     local chk = {}
     for _, data in ipairs(c.automatic_upgraded_from) do
     for _, data in ipairs(c.automatic_recipes) do
         for _, group in ipairs(data.groups) do
         for _, part in ipairs(data.parts) do
             q[#q+1] = group.item_id
             q[#q+1] = part.item_id
             chk[group.item_id] = {
             chk[part.item_id] = {
                 amount=group.amount,
                 amount=part.amount,
                 text=data.text({}, frame),
                 text=data.text(),
             }
             }
         end
         end
Line 3,011: Line 721:
     end
     end
      
      
     tbl = mw.html.create('table')
     local tbl = mw.html.create('table')
     tbl:attr('class', 'wikitable sortable')
     tbl:attr('class', 'wikitable sortable')
     for _, row in ipairs(results) do
     for _, row in ipairs(results) do

Latest revision as of 23:36, 6 October 2024

Module documentation[view] [edit] [history] [purge]


Lua logo

This module depends on the following other modules:

This submodule of Module:Item contains configuration and functions for item recipes.

-------------------------------------------------------------------------------
-- 
-- Recipes for Module:Item
-- 
-------------------------------------------------------------------------------

local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')

local m_game = mw.loadData('Module:Game')

-- Lazy loading
local f_modifier_link -- require('Module:Modifier link').modifier_link

-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Item')

-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Item/config/sandbox') or mw.loadData('Module:Item/config')

local i18n = cfg.i18n.recipes

-- ----------------------------------------------------------------------------
-- Helper functions 
-- ----------------------------------------------------------------------------

local h = {}

-- Lazy loading for Module:Modifier link
function h.modifier_link(args)
    if not f_modifier_link then
        f_modifier_link = require('Module:Modifier link').main
    end
    return f_modifier_link(args)
end

h.conditions = {}
h.conditions.factory = {}

function h.conditions.factory.arg(args)
    -- Required:
    --  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
    --
    -- Optional:
    --  negate: negates the check against the value, i.e. whether the value is not equal or not in the list/table.
    args = args or {}
    
    -- 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)
        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

function h.conditions.factory.not_arg(args)
    args = args or {}
    args.negate = true
    return h.conditions.factory.arg(args)
end

function h.conditions.factory.flag_is_set(args)
    return function (tpl_args)
        return tpl_args._flags[args.flag] == true
    end
end

function h.conditions.factory.acquisition_tag(args)
    return function (tpl_args)
        local negate = args.negate or false
        for _, tag in ipairs(tpl_args.acquisition_tags or {}) do
            if tag == args.tag then
                return not negate
            end
        end
        return negate
    end
end

function h.conditions.factory.drop_monsters(args)
    return function (tpl_args)
        for _, monster in ipairs(tpl_args.drop_monsters or {}) do
            if string.find(monster, args.monster, 1, true) then
                return true
            end
        end
        return false
    end
end

function h.conditions.factory.drop_rarity(args)
    return function (tpl_args)
        for _, rarity in ipairs(tpl_args.drop_rarities_ids or {}) do
            if rarity == args.rarity then
                return true
            end
        end
        return false
    end
end

function h.conditions.factory.drop_level_not_greater_than(args)
    return function (tpl_args)
        if tpl_args.drop_level == nil then
            return true
        end
        return tpl_args.drop_level <= args.level
    end
end

function h.conditions.item_class_has_corrupted_implicits(tpl_args)
    local groups = {
        cfg.class_groups.weapons.keys,
        cfg.class_groups.armor.keys,
        cfg.class_groups.jewellery.keys,
        {['Quiver'] = true, ['Jewel'] = true, ['AbyssJewel'] = true},
    }
    for _, g in ipairs(groups) do
        if h.conditions.factory.arg{arg='class_id', values_assoc=g}(tpl_args) then
            return true
        end
    end
    return false
end

function h.conditions.item_class_has_influences(tpl_args)
    local groups = {
        cfg.class_groups.weapons.keys,
        cfg.class_groups.armor.keys,
        cfg.class_groups.jewellery.keys,
        {['Quiver'] = true},
    }
    for _, g in ipairs(groups) do
        if h.conditions.factory.arg{arg='class_id', values_assoc=g}(tpl_args) and h.conditions.factory.not_arg{arg='class_id', value='FishingRod'}(tpl_args) then
            return true
        end
    end
    return false
end

function h.conditions.item_class_has_synthesised_implicits(tpl_args)
    local groups = {
        cfg.class_groups.weapons.keys,
        cfg.class_groups.armor.keys,
        cfg.class_groups.jewellery.keys,
        {['Quiver'] = true, ['Jewel'] = true, ['AbyssJewel'] = true},
    }
    for _, g in ipairs(groups) do
        if h.conditions.factory.arg{arg='class_id', values_assoc=g}(tpl_args) and h.conditions.factory.not_arg{arg='class_id', value='FishingRod'}(tpl_args) then
            return true
        end
    end
    return false
end

function h.conditions.item_class_has_fractured_modifiers(tpl_args)
    local groups = {
        cfg.class_groups.weapons.keys,
        cfg.class_groups.armor.keys,
        cfg.class_groups.jewellery.keys,
        {['Quiver'] = true, ['Jewel'] = true, ['Map'] = true},
    }
    for _, g in ipairs(groups) do
        if h.conditions.factory.arg{arg='class_id', values_assoc=g}(tpl_args) and h.conditions.factory.not_arg{arg='class_id', value='FishingRod'}(tpl_args) then
            return true
        end
    end
    return false
end

-- ----------------------------------------------------------------------------
-- Additional configuration
-- ----------------------------------------------------------------------------

local c = {}

c.named_conditions = {
    is_normal = h.conditions.factory.arg{arg='rarity_id', value='normal'},
    is_unique = h.conditions.factory.arg{arg='rarity_id', value='unique'},
    is_not_drop_restricted = h.conditions.factory.arg{arg='is_drop_restricted', value=false},
    is_not_corrupted = h.conditions.factory.arg{arg='is_corrupted', value=false},
    is_not_replica = h.conditions.factory.arg{arg='is_replica', value=false},
    drop_level_ngt_divcard_default_max_ilvl = h.conditions.factory.drop_level_not_greater_than{level=cfg.divination_card_exchange_default_max_ilvl},
    item_class_has_corrupted_implicits = h.conditions.item_class_has_corrupted_implicits,
    item_class_has_influences = h.conditions.item_class_has_influences,
    item_class_has_synthesised_implicits = h.conditions.item_class_has_synthesised_implicits,
    item_class_has_fractured_modifiers = h.conditions.item_class_has_fractured_modifiers,
}
-- Order matters!
-- Put most specific outcome at the top and the least specific at the bottom.
c.automatic_recipes = {
--[[
    {
        conditions = {
            function (tpl_args) end,
        },
        text = '',
        parts = {
            {
                name = '',
                item_id = '',
                amount = 0,
                notes = '',
            },
        },
    },
]]
    
}

-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------

local p = {}

function p.process_recipes(tpl_args)
    local query_data = {
        id = {},
        name = {},
        page = {},
    }
    local recipes = {}
    
    -- ------------------------------------------------------------------------
    -- Manual data
    -- ------------------------------------------------------------------------
    local recipe_num = #recipes + 1
    local recipe
    repeat
        local prefix = string.format('recipe%s_', recipe_num)
        local part_num = 1
        local part
        recipe = {
            parts = {},
            result_amount = tonumber(tpl_args[prefix .. 'result_amount']) or 1,
            text = m_util.cast.text(tpl_args[prefix .. 'description']),
            automatic = false,
        }
        repeat 
            local part_prefix = string.format('%spart%s_', prefix, part_num)
            part = {
                item_name = tpl_args[part_prefix .. 'item_name'],
                item_id = tpl_args[part_prefix .. 'item_id'], 
                item_page = tpl_args[part_prefix .. 'item_page'], 
                amount = tonumber(tpl_args[part_prefix .. 'amount']),
                notes = m_util.cast.text(tpl_args[part_prefix .. 'notes']),
            }
            
            if part.item_name ~= nil or part.item_id ~= nil or part.item_page ~= nil then
                if part.amount == nil then
                    error(string.format(i18n.errors.missing_amount, part_prefix .. 'amount'))
                else
                    for key, array in pairs(query_data) do
                        local value = part['item_' .. key]
                        if value then
                            if array[value] then
                                table.insert(array[value], {recipe_num, part_num})
                            else
                                array[value] = {{recipe_num, part_num}, }
                            end
                        end
                    end
                    recipe.parts[#recipe.parts+1] = part
                end
            end
            
            part_num = part_num + 1
        until part.item_name == nil and part.item_id == nil and part.item_page == nil
        
        -- recipe was empty, can terminate safely
        if #recipe.parts == 0 then
            recipe = nil
        else
            recipe_num = recipe_num + 1
            recipes[#recipes+1] = recipe
        end
    until recipe == nil

    -- ------------------------------------------------------------------------
    -- Automatic
    -- ------------------------------------------------------------------------
    
    --
    --  maps
    --
    local automatic_index = #recipes + 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
            recipes[#recipes+1] = {
                text = i18n.misc.upgraded_from_map,
                result_amount = 1,
                parts = {
                    {
                        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
            recipes[#recipes+1] = {
                text = nil,
                result_amount = 1,
                parts = {
                    {
                        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._flags.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
                recipes[#recipes+1] = {
                    automatic = true,
                    result_amount = 1,
                    text = nil,
                    parts = {
                        {
                            item_id = row['items.metadata_id'],
                            item_page = row['items._pageName'],
                            item_name = row['items.name'],
                            amount = 3,
                        },
                    },
                }
                -- corruption +1
                recipes[#recipes+1] = {
                    automatic = true,
                    result_amount = 1,
                    text = i18n.essence_plus_one_level,
                    parts = {
                        {
                            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
                recipes[#recipes+1] = {
                    automatic = true,
                    result_amount = 1,
                    text = i18n.essence_type_change,
                    parts = {
                        {
                            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.disable_automatic_recipes then
        -- Test and cache results of all named conditions
        for k, condition in pairs(c.named_conditions) do
            if type(condition) == 'function' then
                c.named_conditions[k] = condition(tpl_args)
            end
        end

        for _, data in ipairs(c.automatic_recipes) do
            local valid = true -- Can this recipe produce the item?

            -- Check cached results for named conditions
            for k, condition in pairs(c.named_conditions) do
                if data.conditions[k] then
                    valid = condition
                    if not valid then
                        break
                    end
                end
            end

            -- Test anonymous conditions
            for _, condition in ipairs(data.conditions) do
                valid = condition(tpl_args) and valid
                if not valid then
                    break
                end
            end

            if valid then
                recipes[#recipes+1] = {
                    automatic = true,
                    result_amount = 1,
                    text = data.text(),
                    parts = data.parts,
                }
                for part_num, row in ipairs(data.parts) do
                    if query_data['id'][row.item_id] then
                        table.insert(query_data['id'][row.item_id], {#recipes, part_num})
                    else
                        query_data['id'][row.item_id] = {{#recipes, part_num}, }
                    end
                end
            end
        end
    end
    
    if #recipes == 0 then
        return
    end
    --
    -- Fetch item data in a single query to sacrifice database load with a lot of 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 '),
        }
    )

    -- Now do The Void
    for _, row in ipairs(results) do
        if row[query_fields.id] and string.find(row[query_fields.id], 'Metadata/Items/DivinationCards/', 1, true) then
            local part = {
                item_id = 'Metadata/Items/DivinationCards/DivinationCardTheVoid',
                amount = 1,
            }
            local result = m_cargo.query(
                {'items'},
                {'items._pageName',  'items.name', 'items.metadata_id'},
                {
                    where=string.format('%s = "%s"', query_fields.id, part.item_id),
                }
            )
            if #result > 0 then
                recipes[#recipes+1] = {
                    automatic = true,
                    result_amount = 1,
                    text = i18n.the_void,
                    parts = {part},
                }
                if query_data['id'][part.item_id] then
                    table.insert(query_data['id'][part.item_id], {#recipes, 1})
                else
                    query_data['id'][part.item_id] = {{#recipes, 1}, }
                end
                table.insert(results, result[1])
            end
            break
        end
    end

    for _, row in ipairs(results) do
        for key, thing_array in pairs(query_data) do
            local recipe_parts = thing_array[row[query_fields[key]]]
            if recipe_parts then
                for _, recipe_part in ipairs(recipe_parts) do
                    local entry = recipes[recipe_part[1]].parts[recipe_part[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, recipe_parts in pairs(array) do
                for _, recipe_part in ipairs(recipe_parts) do
                    tpl_args._flags.invalid_recipe_parts = true
                    tpl_args._errors[#tpl_args._errors+1] = m_util.string.format(i18n.errors.invalid_recipe_parts, string.format('recipe%s_part%s_item_%s', recipe_part[1], recipe_part[2], key), thing)
                end
            end
        end
    end
    
    --
    -- Check for duplicates
    --
    local delete_recipes = {}
    for i=automatic_index, #recipes do
        for j=1, automatic_index-1 do
            if #recipes[i].parts == #recipes[j].parts then
                local match = true
                for row_id, row in ipairs(recipes[i].parts) 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] == recipes[j].parts[row_id][key])
                    end
                end
                if match then
                    tpl_args._flags.duplicate_recipes = true
                    tpl_args._errors[#tpl_args._errors+1] = string.format(i18n.errors.duplicate_recipes, j)
                    delete_recipes[#delete_recipes+1] = j 
                end
            end
        end
    end
    
    for offset, index in ipairs(delete_recipes) do
        table.remove(recipes, index-(offset-1))
    end

    --
    -- Set data
    -- 
    tpl_args.recipes = recipes
    
    -- Set recipes data
    for i, recipe in ipairs(recipes) do
        table.insert(tpl_args._store_data, {
            _table = 'acquisition_recipes',
            recipe_id = i,
            result_amount = recipe.result_amount,
            description = recipe.text,
            automatic = recipe.automatic,
        })
        for j, part in ipairs(recipe.parts) do
            table.insert(tpl_args._store_data, {
                _table = 'acquisition_recipe_parts',
                part_id = j,
                recipe_id = i,
                item_name = part.item_name,
                item_id = part.item_id,
                item_page = part.item_page,
                amount = part.amount,
                notes = part.notes,
            })
        end
    end
end

--
-- Debugging
--

function p.debug_validate_auto_upgraded_from()
    local q = {}
    local chk = {}
    for _, data in ipairs(c.automatic_recipes) do
        for _, part in ipairs(data.parts) do
            q[#q+1] = part.item_id
            chk[part.item_id] = {
                amount=part.amount,
                text=data.text(),
            }
        end
    end
    
    local results = m_cargo.array_query{
        tables={'items', 'stackables'},
        fields={'items.name', 'items.class_id', 'items.description', 'stackables.stack_size'},
        id_field='items.metadata_id',
        id_array=q,
        query={
            join='items._pageName=stackables._pageName',
        },
    }
    
    for _, row in ipairs(results) do
        if row['items.class_id'] == 'DivinationCard' and chk[row['items.metadata_id']].amount ~= tonumber(row['stackables.stack_size']) then
            mw.logObject(string.format('Amount mismatch %s, expected %s', row['items.metadata_id'], row['stackables.stack_size']))
        end
    end
    
    local tbl = mw.html.create('table')
    tbl:attr('class', 'wikitable sortable')
    for _, row in ipairs(results) do
        tbl
            :tag('tr')
                :tag('td')
                    :wikitext(row['items.name'])
                    :done()
                :tag('td')
                    :wikitext(chk[row['items.metadata_id']].text)
                    :done()
                :tag('td')
                    :wikitext(row['items.description'])
                    :done()
                :done()
    end
    
    return tostring(tbl)
end

return p