Module:Sandbox/Aurelain/ArtifactsOverview

From Heroes of Might and Magic: Olden Era Official Wiki

This module renders a giant table with all artifacts. Scroll artifacts are consolidated into 3 generic scroll artifacts (normal, enchanted and mythic). Campaign artifacts are excluded.

A hyper parameter is supported. For more information, see Module:UtilHyper/doc.

Usage

Input: {{#invoke:Sandbox/Aurelain/ArtifactsOverview|display | lang=en | hyper=Attack=Hero_Basics#Attributes}}

Output:

table#1 {
    ["ambassadors_word_item_set_tier_0"] = table#2 {
        ["descriptions"] = table#3 {
            table#4 {
                ["description"] = "Neutral squads join the wearer’s army for free.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "ambassadors_word_item_set_tier_0",
        ["name"] = "Ambassador's Word",
        ["nameEn"] = "Ambassador's Word",
        ["required_amount"] = "2",
    },
    ["angelic_alliance_item_set_tier_0"] = table#5 {
        ["descriptions"] = table#6 {
            table#7 {
                ["description"] = "Creatures from different factions do not lower each other’s Morale.",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "angelic_alliance_item_set_tier_0",
        ["name"] = "Angelic Alliance",
        ["nameEn"] = "Angelic Alliance",
        ["required_amount"] = "3",
    },
    ["angelic_alliance_item_set_tier_1"] = table#8 {
        ["descriptions"] = table#9 {
            [2] = table#10 {
                ["description"] = "+3 Initiative and Speed to all friendly creatures.",
                ["required_amount"] = 6,
            },
        },
        ["id"] = "angelic_alliance_item_set_tier_1",
        ["name"] = "Angelic Alliance",
        ["nameEn"] = "Angelic Alliance",
        ["required_amount"] = "6",
    },
    ["beelzebubs_blessing_item_set_tier_0"] = table#11 {
        ["descriptions"] = table#12 {
            table#13 {
                ["description"] = "+1 Speed to friendly creatures.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "beelzebubs_blessing_item_set_tier_0",
        ["name"] = "Beelzebub's Chosen",
        ["nameEn"] = "Beelzebub's Chosen",
        ["required_amount"] = "2",
    },
    ["beelzebubs_blessing_item_set_tier_1"] = table#14 {
        ["descriptions"] = table#15 {
            [2] = table#16 {
                ["description"] = "Friendly creatures deal maximum Damage. (If the creature must deal minimum Damage due to another effect, neither is active.)",
                ["required_amount"] = 4,
            },
        },
        ["id"] = "beelzebubs_blessing_item_set_tier_1",
        ["name"] = "Beelzebub's Chosen",
        ["nameEn"] = "Beelzebub's Chosen",
        ["required_amount"] = "4",
    },
    ["duelists_pride_item_set_tier_0"] = table#17 {
        ["descriptions"] = table#18 {
            table#19 {
                ["description"] = "+3 Attack and Defense.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "duelists_pride_item_set_tier_0",
        ["name"] = "Duelist's Pride",
        ["nameEn"] = "Duelist's Pride",
        ["required_amount"] = "2",
    },
    ["duelists_pride_item_set_tier_1"] = table#20 {
        ["descriptions"] = table#21 {
            [2] = table#22 {
                ["description"] = "All friendly creatures have unlimited counterattacks.",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "duelists_pride_item_set_tier_1",
        ["name"] = "Duelist's Pride",
        ["nameEn"] = "Duelist's Pride",
        ["required_amount"] = "3",
    },
    ["elixir_of_life_item_set_tier_0"] = table#23 {
        ["descriptions"] = table#24 {
            table#25 {
                ["description"] = "Friendly creatures heal 2.5% of their starting HP at the start of each round. This can revive fallen units.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "elixir_of_life_item_set_tier_0",
        ["name"] = "Anesthesia",
        ["nameEn"] = "Anesthesia",
        ["required_amount"] = "2",
    },
    ["elixir_of_life_item_set_tier_1"] = table#26 {
        ["descriptions"] = table#27 {
            [2] = table#28 {
                ["description"] = "+25% maximum HP to all friendly creatures.",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "elixir_of_life_item_set_tier_1",
        ["name"] = "Anesthesia",
        ["nameEn"] = "Anesthesia",
        ["required_amount"] = "3",
    },
    ["ethereal_knowledge_item_set_tier_0"] = table#29 {
        ["descriptions"] = table#30 {
            table#31 {
                ["description"] = "+3 Spell Power and Knowledge.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "ethereal_knowledge_item_set_tier_0",
        ["name"] = "Ethereal Knowledge",
        ["nameEn"] = "Ethereal Knowledge",
        ["required_amount"] = "2",
    },
    ["ethereal_knowledge_item_set_tier_1"] = table#32 {
        ["descriptions"] = table#33 {
            [2] = table#34 {
                ["description"] = "All effects applied by the hero and friendly creatures last +{0} round(s).",
                ["required_amount"] = 4,
            },
        },
        ["id"] = "ethereal_knowledge_item_set_tier_1",
        ["name"] = "Ethereal Knowledge",
        ["nameEn"] = "Ethereal Knowledge",
        ["required_amount"] = "4",
    },
    ["gifts_of_dwarven_lords_item_set_tier_0"] = table#35 {
        ["descriptions"] = table#36 {
            table#37 {
                ["description"] = "–1 level(s) to all enemy Magic School spells.",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "gifts_of_dwarven_lords_item_set_tier_0",
        ["name"] = "Dwarven Fortitude",
        ["nameEn"] = "Dwarven Fortitude",
        ["required_amount"] = "3",
    },
    ["gifts_of_dwarven_lords_item_set_tier_1"] = table#38 {
        ["descriptions"] = table#39 {
            [2] = table#40 {
                ["description"] = "Makes the wearer immune to all negative effects in battle and on the global map.",
                ["required_amount"] = 4,
            },
        },
        ["id"] = "gifts_of_dwarven_lords_item_set_tier_1",
        ["name"] = "Dwarven Fortitude",
        ["nameEn"] = "Dwarven Fortitude",
        ["required_amount"] = "4",
    },
    ["inner_song_item_set_tier_0"] = table#41 {
        ["descriptions"] = table#42 {
            table#43 {
                ["description"] = "When friendly creatures use abilities, their turn doesn’t end.",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "inner_song_item_set_tier_0",
        ["name"] = "Inner Song",
        ["nameEn"] = "Inner Song",
        ["required_amount"] = "3",
    },
    ["keepers_fortitude_item_set_tier_0"] = table#44 {
        ["descriptions"] = table#45 {
            table#46 {
                ["description"] = "+4 Defense.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "keepers_fortitude_item_set_tier_0",
        ["name"] = "Keeper's Fortitude",
        ["nameEn"] = "Keeper's Fortitude",
        ["required_amount"] = "2",
    },
    ["knights_honor_item_set_tier_0"] = table#47 {
        ["descriptions"] = table#48 {
            table#49 {
                ["description"] = "+1 Morale and Luck.",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "knights_honor_item_set_tier_0",
        ["name"] = "Knight's Honor",
        ["nameEn"] = "Knight's Honor",
        ["required_amount"] = "3",
    },
    ["knights_honor_item_set_tier_1"] = table#50 {
        ["descriptions"] = table#51 {
            [2] = table#52 {
                ["description"] = "+3% chance of Morale and Luck triggering for each point of them the wearer has.",
                ["required_amount"] = 5,
            },
        },
        ["id"] = "knights_honor_item_set_tier_1",
        ["name"] = "Knight's Honor",
        ["nameEn"] = "Knight's Honor",
        ["required_amount"] = "5",
    },
    ["legendary_hero_item_set_tier_0"] = table#53 {
        ["descriptions"] = table#54 {
            table#55 {
                ["description"] = "When the hero uses Heroic Strike, its effects apply twice.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "legendary_hero_item_set_tier_0",
        ["name"] = "Hero of Tales",
        ["nameEn"] = "Hero of Tales",
        ["required_amount"] = "2",
    },
    ["living_arrows_item_set_tier_0"] = table#56 {
        ["descriptions"] = table#57 {
            table#58 {
                ["description"] = "+1 Initiative to all friendly creatures.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "living_arrows_item_set_tier_0",
        ["name"] = "Living Arrows",
        ["nameEn"] = "Living Arrows",
        ["required_amount"] = "2",
    },
    ["living_arrows_item_set_tier_1"] = table#59 {
        ["descriptions"] = table#60 {
            [2] = table#61 {
                ["description"] = "Creatures with Ranged attacks gain “Double Shot.”",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "living_arrows_item_set_tier_1",
        ["name"] = "Living Arrows",
        ["nameEn"] = "Living Arrows",
        ["required_amount"] = "3",
    },
    ["milos_curse_item_set_tier_0"] = table#62 {
        ["descriptions"] = table#63 {
            table#64 {
                ["description"] = "+4000 Gold daily.",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "milos_curse_item_set_tier_0",
        ["name"] = "Milos's Curse",
        ["nameEn"] = "Milos's Curse",
        ["required_amount"] = "3",
    },
    ["paupers_glory_item_set_tier_0"] = table#65 {
        ["descriptions"] = table#66 {
            table#67 {
                ["description"] = "+1 Attack, Defense, Spell Power, Knowledge, Morale, and Luck.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "paupers_glory_item_set_tier_0",
        ["name"] = "Pauper's Glory",
        ["nameEn"] = "Pauper's Glory",
        ["required_amount"] = "2",
    },
    ["paupers_glory_item_set_tier_1"] = table#68 {
        ["descriptions"] = table#69 {
            [2] = table#70 {
                ["description"] = "+1 more Attack, Defense, Spell Power, Knowledge, Morale, and Luck.",
                ["required_amount"] = 4,
            },
        },
        ["id"] = "paupers_glory_item_set_tier_1",
        ["name"] = "Pauper's Glory",
        ["nameEn"] = "Pauper's Glory",
        ["required_amount"] = "4",
    },
    ["paupers_glory_item_set_tier_2"] = table#71 {
        ["descriptions"] = table#72 {
            [3] = table#73 {
                ["description"] = "+1 more Attack, Defense, Spell Power, Knowledge, Morale, and Luck.",
                ["required_amount"] = 6,
            },
        },
        ["id"] = "paupers_glory_item_set_tier_2",
        ["name"] = "Pauper's Glory",
        ["nameEn"] = "Pauper's Glory",
        ["required_amount"] = "6",
    },
    ["power_of_the_dragon_father_item_set_tier_0"] = table#74 {
        ["descriptions"] = table#75 {
            table#76 {
                ["description"] = "+2 to all attributes.",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "power_of_the_dragon_father_item_set_tier_0",
        ["name"] = "Power of the Dragon Father",
        ["nameEn"] = "Power of the Dragon Father",
        ["required_amount"] = "3",
    },
    ["power_of_the_dragon_father_item_set_tier_1"] = table#77 {
        ["descriptions"] = table#78 {
            [2] = table#79 {
                ["description"] = "Friendly creatures are immune to all negative effects.",
                ["required_amount"] = 5,
            },
        },
        ["id"] = "power_of_the_dragon_father_item_set_tier_1",
        ["name"] = "Power of the Dragon Father",
        ["nameEn"] = "Power of the Dragon Father",
        ["required_amount"] = "5",
    },
    ["power_of_the_dragon_father_item_set_tier_2"] = table#80 {
        ["descriptions"] = table#81 {
            [3] = table#82 {
                ["description"] = "Friendly creatures are immune to all Magic Damage.",
                ["required_amount"] = 8,
            },
        },
        ["id"] = "power_of_the_dragon_father_item_set_tier_2",
        ["name"] = "Power of the Dragon Father",
        ["nameEn"] = "Power of the Dragon Father",
        ["required_amount"] = "8",
    },
    ["rule_of_shadow_item_set_tier_0"] = table#83 {
        ["descriptions"] = table#84 {
            table#85 {
                ["description"] = "+1 round(s) to the cooldown of all enemy spells.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "rule_of_shadow_item_set_tier_0",
        ["name"] = "Dominion of Puppeteers",
        ["nameEn"] = "Dominion of Puppeteers",
        ["required_amount"] = "2",
    },
    ["rule_of_shadow_item_set_tier_1"] = table#86 {
        ["descriptions"] = table#87 {
            [2] = table#88 {
                ["description"] = "All spells in the enemy’s Spellbook have minimum level. (If all spells must have maximum level due to another effect, neither is active.)",
                ["required_amount"] = 4,
            },
        },
        ["id"] = "rule_of_shadow_item_set_tier_1",
        ["name"] = "Dominion of Puppeteers",
        ["nameEn"] = "Dominion of Puppeteers",
        ["required_amount"] = "4",
    },
    ["scholars_wisdom_item_set_tier_0"] = table#89 {
        ["descriptions"] = table#90 {
            table#91 {
                ["description"] = "+4 Knowledge.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "scholars_wisdom_item_set_tier_0",
        ["name"] = "Scholar's Wisdom",
        ["nameEn"] = "Scholar's Wisdom",
        ["required_amount"] = "2",
    },
    ["shadow_of_death_item_set_tier_0"] = table#92 {
        ["descriptions"] = table#93 {
            table#94 {
                ["description"] = "–1 Morale and Luck to all enemy creatures.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "shadow_of_death_item_set_tier_0",
        ["name"] = "Shadow of Death",
        ["nameEn"] = "Shadow of Death",
        ["required_amount"] = "2",
    },
    ["shadow_of_death_item_set_tier_1"] = table#95 {
        ["descriptions"] = table#96 {
            [2] = table#97 {
                ["description"] = "–1 Initiative and Speed to all enemy creatures.",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "shadow_of_death_item_set_tier_1",
        ["name"] = "Shadow of Death",
        ["nameEn"] = "Shadow of Death",
        ["required_amount"] = "3",
    },
    ["shadow_of_death_item_set_tier_2"] = table#98 {
        ["descriptions"] = table#99 {
            [3] = table#100 {
                ["description"] = "All enemy creatures deal minimal Damage. (If enemy creatures must deal maximum Damage due to another effect, neither is active.)",
                ["required_amount"] = 4,
            },
        },
        ["id"] = "shadow_of_death_item_set_tier_2",
        ["name"] = "Shadow of Death",
        ["nameEn"] = "Shadow of Death",
        ["required_amount"] = "4",
    },
    ["shamaniac_soul_item_set_tier_0"] = table#101 {
        ["descriptions"] = table#102 {
            table#103 {
                ["description"] = "+25% maximum mana.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "shamaniac_soul_item_set_tier_0",
        ["name"] = "Shamanic Soul",
        ["nameEn"] = "Shamanic Soul",
        ["required_amount"] = "2",
    },
    ["shamaniac_soul_item_set_tier_1"] = table#104 {
        ["descriptions"] = table#105 {
            [2] = table#106 {
                ["description"] = "All spells in the Spellbook have maximum level. (If all spells must have minimum level due to another effect, neither is active.)",
                ["required_amount"] = 4,
            },
        },
        ["id"] = "shamaniac_soul_item_set_tier_1",
        ["name"] = "Shamanic Soul",
        ["nameEn"] = "Shamanic Soul",
        ["required_amount"] = "4",
    },
    ["tranquility_item_set_tier_0"] = table#107 {
        ["descriptions"] = table#108 {
            table#109 {
                ["description"] = "Fully restores the hero’s mana at the start of each day.",
                ["required_amount"] = 3,
            },
        },
        ["id"] = "tranquility_item_set_tier_0",
        ["name"] = "Tranquil Glass",
        ["nameEn"] = "Tranquil Glass",
        ["required_amount"] = "3",
    },
    ["ukhtabar_seal_item_set_tier_0"] = table#110 {
        ["descriptions"] = table#111 {
            table#112 {
                ["description"] = "+50% XP gain.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "ukhtabar_seal_item_set_tier_0",
        ["name"] = "Ukhtabar Signet",
        ["nameEn"] = "Ukhtabar Signet",
        ["required_amount"] = "2",
    },
    ["wanderers_way_item_set_tier_0"] = table#113 {
        ["descriptions"] = table#114 {
            table#115 {
                ["description"] = "Eliminates all terrain penalties.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "wanderers_way_item_set_tier_0",
        ["name"] = "Wanderer's Way",
        ["nameEn"] = "Wanderer's Way",
        ["required_amount"] = "2",
    },
    ["warriors_strength_item_set_tier_0"] = table#116 {
        ["descriptions"] = table#117 {
            table#118 {
                ["description"] = "+4 Attack.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "warriors_strength_item_set_tier_0",
        ["name"] = "Warrior's Strength",
        ["nameEn"] = "Warrior's Strength",
        ["required_amount"] = "2",
    },
    ["wizards_might_item_set_tier_0"] = table#119 {
        ["descriptions"] = table#120 {
            table#121 {
                ["description"] = "+4 Spell Power.",
                ["required_amount"] = 2,
            },
        },
        ["id"] = "wizards_might_item_set_tier_0",
        ["name"] = "Wizard's Might",
        ["nameEn"] = "Wizard's Might",
        ["required_amount"] = "2",
    },
}

-- Usage: see Module:ArtifactsOverview/doc
local p = {}
local f = mw.ustring.format

local SLOT_ORDER = {
    left_hand = 1,  -- Main Hand
    right_hand = 2, -- Off Hand
    armor = 3,      -- Armor
    head = 4,       -- Head
    boots = 5,      -- Boots
    belt = 6,       -- Belt
    back = 7,       -- Back
    ring = 8,       -- Rings
    item_slot = 9,  -- Item
    unic_slot = 10, -- Relic
}

local RARITY_ORDER = {
    common = 1,
    rare = 2,
    epic = 3,
    legendary = 4,
}

local SLOT_ICONS = {
    armor = 'Icon_Equipment_Armor.png',
    back = 'Icon_Equipment_Cloak.png',
    belt = 'Icon_Equipment_Belt.png',
    boots = 'Icon_Equipment_Boots.png',
    head = 'Icon_Equipment_Helmet.png',
    item_slot = 'Icon_Equipment_Sundries.png',
    left_hand = 'Icon_Equipment_RightHand.png',
    right_hand = 'Icon_Equipment_LeftHand.png',
    ring = 'Icon_Equipment_Rings.png',
    unic_slot = 'Icon_Equipment_Banner.png',
}

-- Note: The right side is just a fallback, it will seldom be used.
local TRANSLATION_IDS = {
    wiki_none = 'None',
    wiki_level = 'Level',
    wiki_cost = 'Cost',
    wiki_total = 'Total',
    wiki_ao_artifact = 'Artifact',
    unit_window_narrative = 'Description',
    wiki_ao_upgrade = 'Upgrade', -- inventoryUpgradeItemLabel
    wiki_ao_slot = 'Slot',
    wiki_ao_rarity = 'Rarity',
    wiki_ao_set = 'Set',
    wiki_ao_upgrade_cost = 'Upgrade cost',
    wiki_ao_armor = 'Armor',
    wiki_ao_back = 'Back',
    wiki_ao_belt = 'Belt',
    wiki_ao_boots = 'Boots',
    wiki_ao_head = 'Head',
    wiki_ao_item_slot = 'Item',
    wiki_ao_left_hand = 'Main Hand',
    wiki_ao_right_hand = 'Off Hand',
    wiki_ao_ring = 'Rings',
    wiki_ao_unic_slot = 'Relic',
    wiki_ao_common = 'Common',
    wiki_ao_rare = 'Rare',
    wiki_ao_epic = 'Epic',
    wiki_ao_legendary = 'Legendary',
    wiki_ao_special = 'Special',
}

local ROMAN = { 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII' }

-- Displays a variable
------------------------------------------------------------------------------------------------------------------------
local function dump(target)
    return '<pre>' .. mw.dumpObject(target) .. '</pre>'
end

-- Spawns a safe robotic url from an English name, fit for adding it before the visible name
------------------------------------------------------------------------------------------------------------------------
local function getUrl(nameEn, lang)
    if not nameEn then
        return ''
    end
    local url = nameEn
    if lang ~= 'en' then
        url = url .. '/' .. lang
    end
    return url
end

-- Retrieves the text for some specific ids from Cargo Translations.
------------------------------------------------------------------------------------------------------------------------
local function translateIds(ids, lang, extra)
    -- Key list
    local list = {}
    for key, _ in pairs(ids) do
        list[#list + 1] = key
    end
    local idListString = '"' .. table.concat(list, '", "') .. '"'

    -- Cargo
    local where = {}
    table.insert(where, 'target_id IN (' .. idListString .. ')')
    table.insert(where, 'language = "' .. lang .. '"')
    if extra then
        table.insert(where, extra)
    end
    local results = mw.ext.cargo.query('Translation', 'target_id, name', {
        where = table.concat(where, ' AND '),
        limit = 100
    })

    -- Dictionary
    local dictionary = mw.clone(ids)
    for key, value in pairs(dictionary) do
        dictionary[key] = value .. '🗯️'
    end
    for _, row in ipairs(results) do
        local name = row['name']
        if name and name ~= '' then
            dictionary[row['target_id']] = name
        end
    end
    return dictionary
end

-- Converts a dictionary into a linear array
------------------------------------------------------------------------------------------------------------------------
local function flatten(hub)
    local list = {}
    for _, entry in pairs(hub) do
        list[#list + 1] = entry
    end
    return list
end

-- Parses and runs a Cargo query from wiki-like syntax
------------------------------------------------------------------------------------------------------------------------
local function runQuery(query, ...)
    query = string.gsub(query, "%-%-.-\n", "") -- remove "comments"
    query = select('#', ...) > 0 and mw.ustring.format(query, ...) or query
    query = mw.text.trim(query)
    local tables = ''
    local fields = ''
    local options = {}
    for part in query:gmatch('[^|]+') do
        local key, val = part:match('^%s*([^=]+)%s*=%s*(.-)%s*$')
        if key and val then
            key = mw.text.trim(key):lower()
            val = mw.text.trim(val)
            val = val:gsub('%s', ' ')
            if key == 'tables' then
                tables = val
            elseif key == 'fields' then
                fields = val
            elseif key == 'limit' then
                options[key] = tonumber(val) or val
            elseif key == 'join on' then
                options['join'] = val
            else
                options[key] = val
            end
        end
    end
    return mw.ext.cargo.query(tables, fields, options)
end

-- Interrogates the main Cargo table
------------------------------------------------------------------------------------------------------------------------
local function queryMain(lang)
    return runQuery([[
        tables =
            Artifact = A,
            Translation = T,
            Translation = E
        | join on =
            A.id = T.target_id,
            A.id = E.target_id
        | fields =
            A.id = id,
            A.slot = slot,
            A.rarity = rarity,
            A.artifact_set_id = artifact_set_id,
            A.max_level = max_level,
            A.cost_base = cost_base,
            A.cost_per_level = cost_per_level,
            T.language = language,
            T.name = name,
            T.description = description,
            T.type = translation_type,
            E.name = nameEn,
        | where =
            A.id NOT LIKE 'campaign_%%'
            AND T.language = '%s'
            AND E.type = 'artifact'
            AND E.language = 'en'
        | limit =
            1000
    ]], lang);
end

-- Folds magic scrolls into 3 artifacts (normal, enchanted and mythic):
------------------------------------------------------------------------------------------------------------------------
local function consolidateScrolls(lang, results)
    local hub = {}
    for _, row in ipairs(results) do
        local id = row.id
        if id:find('magic_scroll') then
            if id:find('mythic') then
                id = 'mythic_scroll_box_artifact'
            else
                if id:find('enchanted') then
                    id = 'enchanted_magic_scroll_artifact'
                else
                    id = 'magic_scroll_artifact'
                end
            end
            local entry = hub[id]
            if not entry then
                entry = {}
                entry.id = id or ''
                entry.name = row.name or ''
                entry.nameEn = row.nameEn or ''
                entry.spells = {}
                entry.slot = row.slot or ''
                entry.rarity = row.rarity or ''
                entry.artifact_set_id = row.artifact_set_id or ''
                entry.max_level = 0
                entry.cost_base = 0
                entry.cost_per_level = 0
                entry.description = '' -- will be overridden below
                entry.upgrade = ''
                entry.narrative = ''   -- will be overridden below
                hub[id] = entry
            end
            if row.translation_type == 'artifact' then
                local spellId = row.id:gsub('.*artifact_', '')
                table.insert(entry.spells, spellId)
                entry.description = row.description or ''
            end
            if row.translation_type == 'artifact_narrative' then
                entry.narrative = row.description or ''
            end
        end
    end
    return hub
end

-- Folds all Cargo results into a single dictionary:
------------------------------------------------------------------------------------------------------------------------
local function consolidateMain(lang, results)
    local hub = consolidateScrolls(lang, results)
    for _, row in ipairs(results) do
        local id = row.id
        if not id:find('magic_scroll') then
            local entry = hub[id]
            if not entry then
                entry = {}
                entry.id = id or ''
                entry.name = row.name or ''
                entry.nameEn = row.nameEn or ''
                entry.spells = {}
                entry.slot = row.slot or ''
                entry.rarity = row.rarity or ''
                entry.artifact_set_id = row.artifact_set_id or ''
                entry.max_level = tonumber(row.max_level) or 0
                entry.cost_base = tonumber(row.cost_base) or 0
                entry.cost_per_level = tonumber(row.cost_per_level) or 0
                entry.description = '' -- will be overridden below
                entry.upgrade = ''     -- will be overridden below
                entry.narrative = ''   -- will be overridden below
                hub[id] = entry
            end
            if row.translation_type == 'artifact' then
                entry.description = row.description or ''
            end
            if row.translation_type == 'artifact_upgrade' then
                entry.upgrade = row.description or ''
            end
            if row.translation_type == 'artifact_narrative' then
                entry.narrative = row.description or ''
            end
        end
    end
    return hub
end

-- Interrogates the ItemSetTier Cargo table
------------------------------------------------------------------------------------------------------------------------
local function querySets(lang)
    return runQuery([[
        tables =
            ItemSetTier = S,
            Translation = T,
            Translation = E,
            Translation = L,
        | join on =
            S.set_id = T.target_id,
            S.set_id = E.target_id,
            S.id = L.target_id
        | fields =
            S.id = id,
            S.set_id = set_id,
            S.ordinal = ordinal,
            S.required_amount = required_amount,
            T.language = language,
            T.name = name,
            E.name = nameEn,
            L.description = description
        | where =
            T.language = '%s'
            AND E.language = 'en'
            AND L.language = '%s'
        | limit =
            1000
    ]], lang, lang);
end

-- Folds Set results into a single dictionary:
------------------------------------------------------------------------------------------------------------------------
local function consolidateSets(results)
    local hub = {}
    for _, row in ipairs(results) do
        local id = row.id
        local entry = hub[id]
        if not entry then
            entry = {}
            entry.id = id or ''
            entry.name = row.name or ''
            entry.nameEn = row.nameEn or ''
            entry.required_amount = row.required_amount or ''
            entry.descriptions = {} -- will be filled below
            hub[id] = entry
        end
        local i = tonumber(row.ordinal or 0) + 1
        entry.descriptions[i] = {
            required_amount = tonumber(row.required_amount or 0),
            description = row.description or ''
        }
    end
    return hub
end

------------------------------------------------------------------------------------------------------------------------
local function sortList(a, b)
    local slotA = SLOT_ORDER[a.slot] or 99
    local slotB = SLOT_ORDER[b.slot] or 99
    if slotA ~= slotB then
        return slotA < slotB
    end

    local rarityA = RARITY_ORDER[a.rarity] or 99
    local rarityB = RARITY_ORDER[b.rarity] or 99
    if rarityA ~= rarityB then
        return rarityA < rarityB
    end

    return a.id < b.id
end

-- Boilerplate for the header cells
------------------------------------------------------------------------------------------------------------------------
local function addTh(tr, text)
    return tr:tag('th'):wikitext(text)
end

-- Boilerplate for the body cells
------------------------------------------------------------------------------------------------------------------------
local function addTd(tr, content)
    if type(content) == "string" then
        return tr:tag('td'):wikitext(content)
    else
        return tr:tag('td'):node(content)
    end
end

-- Boilerplate for the separator rows
------------------------------------------------------------------------------------------------------------------------
local function addSeparator(htmlTable, className, content)
    htmlTable:tag('tr')
        :addClass('separator')
        :addClass(className)
        :tag('td'):attr('data-sort-value', ''):attr('colspan', 100):wikitext(content):done()
end

-- TABLE HEADER
------------------------------------------------------------------------------------------------------------------------
local function createHeader(htmlTable, words)
    local tr = htmlTable:tag('tr')
    --addTh(tr, 'id')
    addTh(tr, words.wiki_ao_artifact):attr('colspan', 2)
    addTh(tr, words.unit_window_narrative)
    addTh(tr, words.wiki_ao_upgrade)
    addTh(tr, words.wiki_ao_upgrade_cost)
    addTh(tr, words.wiki_ao_slot)
    addTh(tr, words.wiki_ao_rarity)
    addTh(tr, words.wiki_ao_set)
    --addTh(tr, 'narrative')
end

------------------------------------------------------------------------------------------------------------------------
local function renderIcon(id, nameEn, lang)
    local url = getUrl(nameEn, lang)
    return f('[[File:%s.png|64px|link=%s]]', id, url)
end

------------------------------------------------------------------------------------------------------------------------
local function renderName(name, nameEn, lang)
    local url = getUrl(nameEn, lang)
    return f('[[%s|%s]]', url, name)
end

------------------------------------------------------------------------------------------------------------------------
local function renderDescription(description)
    description = mw.ustring.gsub(description, '[.。]-$', '') -- remove the ending dot
    -- TODO: add links
    return mw.text.trim(description)
end

------------------------------------------------------------------------------------------------------------------------
local function renderUpgrade(item, words, lang)
    if item.max_level < 2 then
        return ''
    end
    if item.id:find('goose_egg') then
        local url = getUrl('Golden_Goose_Egg', lang)
        return f('[[%s|%s]]', url, words.wiki_ao_special)
    end
    local text = item.upgrade
    text = mw.ustring.gsub(text, '^.-[::]', '') -- remove the "Upgrade: " prefix
    if item.max_level < 900 then
        text = mw.ustring.gsub(text, '[.。]-$', '') -- remove the ending dot
    end
    --if item.max_level > 900 then
    --text = mw.ustring.gsub(text, '[.。].-$', '') -- remove the last sentence which should say "Can be upgraded..."
    --end
    return mw.text.trim(text)
end

------------------------------------------------------------------------------------------------------------------------
local function buildUpgradeHint(item, words, icon)
    local t = mw.html.create('table')
    t:tag('tr')
        :tag('th'):wikitext(words.wiki_level)
        :tag('th'):wikitext(words.wiki_cost)
        :tag('th'):wikitext(words.wiki_total)
    local sum = 0
    for i = 1, 9 do
        local current
        if item.cost_per_level == 0 then
            current = item.cost_base
        else
            current = i * (item.cost_base + item.cost_per_level)
        end
        sum = sum + current
        t:tag('tr')
            :tag('th'):wikitext(i + 1)
            :tag('td'):wikitext(current .. icon)
            :tag('td'):wikitext(sum .. icon)
    end
    return tostring(t)
end

------------------------------------------------------------------------------------------------------------------------
local function addUpgradeCost(tr, item, words, frame)
    local icon = '[[File:Icon_Resource_AlchemicalDust.png|24px|link=]]';
    if item.max_level > 900 then
        local symbol = item.cost_per_level == 0 and '' or '+'
        local nr = item.cost_per_level == 0 and item.cost_base or (item.cost_base + item.cost_per_level)
        local text = f('<span class{{=}}"infinity">∞</span> <span class{{=}}"step">(%s%s%s)</span>', symbol, nr, icon)
        local hint = buildUpgradeHint(item, words, icon)
        local wikitext = frame:preprocess(f('{{Hint|%s|%s|side=right|class=floating-costs}}', text, hint))
        tr:tag('td'):wikitext(wikitext):attr('data-sort-value', 1000 + item.cost_per_level)
    elseif item.max_level < 2 then
        tr:tag('td'):attr('data-sort-value', 0)
    else
        local cost = item.cost_base + item.cost_per_level
        tr:tag('td'):wikitext(cost .. icon):attr('data-sort-value', cost)
    end
end

------------------------------------------------------------------------------------------------------------------------
local function renderSlot(item, words, frame)
    local file = f('[[File:%s|48px|link=]]', SLOT_ICONS[item.slot])
    local hint = words['wiki_ao_' .. item.slot]
    return frame:preprocess(f('{{Hint|%s|%s}}', file, hint))
end

------------------------------------------------------------------------------------------------------------------------
local function renderRarity(item, words, frame)
    local text = ROMAN[RARITY_ORDER[item.rarity]]
    local hint = words['wiki_ao_' .. item.rarity]
    return frame:preprocess(f('{{Hint|%s|%s}}', text, hint))
end

------------------------------------------------------------------------------------------------------------------------
local function renderSlotSeparator(slot, words)
    local file = f('[[File:%s|64px|link=]]', SLOT_ICONS[slot])
    local text = words['wiki_ao_' .. slot]
    return file .. text
end

-- TABLE BODY
------------------------------------------------------------------------------------------------------------------------
local function createBody(htmlTable, list, lang, words, frame)
    local currentRarity = list[1].rarity
    local currentSlot = list[1].slot
    for _, item in ipairs(list) do
        -- separators
        if item.slot ~= currentSlot then
            currentSlot = item.slot
            currentRarity = item.rarity
            addSeparator(htmlTable, 'separator-large', renderSlotSeparator(currentSlot, words))
        elseif item.rarity ~= currentRarity then
            currentRarity = item.rarity
            addSeparator(htmlTable, 'separator-tiny ' .. currentRarity, '')
        end

        local tr = htmlTable:tag('tr'):addClass('artifact'):addClass(item.rarity)
        --addTd(tr, item.id)
        addTd(tr, renderIcon(item.id, item.nameEn, lang))
        addTd(tr, renderName(item.name, item.nameEn, lang))
        addTd(tr, renderDescription(item.description))
        addTd(tr, renderUpgrade(item, words, lang))
        addUpgradeCost(tr, item, words, frame)
        addTd(tr, renderSlot(item, words, frame)):attr('data-sort-value', SLOT_ORDER[item.slot])
        addTd(tr, renderRarity(item, words, frame)):attr('data-sort-value', RARITY_ORDER[item.rarity])
        addTd(tr, item.artifact_set_id)
        --addTd(tr, item.narrative)
    end
end

-- M A I N
------------------------------------------------------------------------------------------------------------------------
function p.display(frame)
    --do return dump(frame) end

    -- Args
    local args = frame.args
    local lang = args.lang ~= '' and args.lang or 'en'
    --lang = 'fr'

    -- Language
    local words = translateIds(TRANSLATION_IDS, lang)

    local setsResults = querySets(lang)
    local setsHub = consolidateSets(setsResults)
    do return dump(setsHub) end

    -- Cargo
    local results = queryMain(lang)
    local hub = consolidateMain(lang, results)
    local list = flatten(hub);
    if #list == 0 then return words.wiki_none end

    -- Various manipulations
    table.sort(list, sortList)

    -- Table
    local htmlTable = mw.html.create('table')
    htmlTable:addClass('wikitable sortable table-nobands')
    createHeader(htmlTable, words)
    createBody(htmlTable, list, lang, words, frame)

    -- Output
    local styleTag = frame:extensionTag('templatestyles', '', { src = frame:getTitle() .. '/styles.css' })
    return '<div class="artifacts-overview">' .. styleTag .. tostring(htmlTable) .. '</div>'
end

return p