Module:Sandbox/Aurelain/ArtifactsOverview: Difference between revisions

From Heroes of Might and Magic: Olden Era Official Wiki
No edit summary
No edit summary
Line 530: Line 530:
         addTd(tr, renderSlot(item, words, frame)):attr('data-sort-value', SLOT_ORDER[item.slot])
         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, renderRarity(item, words, frame)):attr('data-sort-value', RARITY_ORDER[item.rarity])
         addTd(tr, renderSet(item.artifact_set_id, setsHub))
         addTd(tr, renderSet(item.artifact_set_id, setsHub)):attr('data-sort-value', item.artifact_set_id)
         --addTd(tr, item.narrative)
         --addTd(tr, item.narrative)
     end
     end

Revision as of 06:15, 1 July 2026

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:

ArtifactDescriptionUpgradeUpgrade costSlotRaritySet
Fish-to-the-Face+1 Attack+1 more Attack25Main HandICommonPauper's Glory
RapierFriendly creatures deal +10% Melee Damage+5% more Melee Damage50Main HandIIRareDuelist's Pride
Lance+3 Attack+2 more Attack50Main HandIIRareKnight's Honor
Ogre's Club of Havoc+4 Attack+2 more Attack. Can be upgraded an unlimited number of times. (+120)
LevelCostTotal
2120120
3240360
4360720
54801200
66001800
77202520
88403360
99604320
1010805400
Main HandIIRare
Red Dragon Flame Tongue+2 Attack and Defense+1 to both attributes50Main HandIIRarePower of the Dragon Father
Garotte–1 Speed to all enemiesMain HandIIIEpic
Shroomwood BowEliminates Ranged Damage penalty for friendly creaturesFriendly creatures deal +5% Ranged Damage75Main HandIIIEpicLiving Arrows
Truthmaker–10% Attack to enemy creatures–5% more Attack75Main HandIIIEpicDominion of Puppeteers
Dark Hatchet+4 Attack, then +15% total Attack+2 Attack75Main HandIIIEpicShadow of Death
Shaman Staff+6 Spell Power+3 more Spell Power75Main HandIIIEpicShamanic Soul
Sword of Judgement+2 Attack, Defense, Spell Power, and Knowledge+1 more to every attribute100Main HandIVLegendaryAngelic Alliance
Demon Claw+12 Attack, but –4 Defense+4 more Attack100Main HandIVLegendaryBeelzebub's Chosen
ExcaliburHeroic Strike kills +1 unit(s). Works on creatures up to tier 7+1 more unit(s)100Main HandIVLegendaryHero of Tales
Off Hand
BucklerFriendly creatures can counterattack even if the attacking enemy has “Swift Strike.”Friendly creatures deal +5% Melee Damage25Off HandICommonDuelist's Pride
Singing Pan-PipeAll effects applied by friendly creatures last +1 round(s)+1 more round(s)25Off HandICommonInner Song
Misericorde+2 Attack+1 more Attack25Off HandICommonKnight's Honor
Shoddy Shield+2 DefenseOff HandICommon
CaduceusHeroic Strike restores 4 mana+4 more mana50Off HandIIRareHero of Tales
Crescent DaggerSpells and creature abilities ignore 40% of enemy magic resistanceIgnore +20% more magic resistance50Off HandIIRareEthereal Knowledge
Fine Wand+1 level to all spellsOff HandIIRare
Pelagic HourglassThe cooldowns of all spells are reduced by 1 round(s)Off HandIIRare
Shield of the Rampaging Ogre+4 Defense+2 more Defense. Can be upgraded an unlimited number of times. (+120)
LevelCostTotal
2120120
3240360
4360720
54801200
66001800
77202520
88403360
99604320
1010805400
Off HandIIRare
Dragon Scale Shield+3 Attack and Defense+1 to both attributes75Off HandIIIEpicPower of the Dragon Father
Truthseeker–10% Defense to enemy creatures–5% more Defense75Off HandIIIEpicDominion of Puppeteers
Lion's Shield of Courage+2 Attack, Defense, Spell Power, and Knowledge+1 more to every attribute100Off HandIVLegendaryAngelic Alliance
Chitinous Shield+12 Defense, but –4 Attack+4 more Defense100Off HandIVLegendaryBeelzebub's Chosen
Armor
Chain Mail+3 DefenseArmorICommon
Rags+1 Spell Power+1 more Spell Power25ArmorICommonPauper's Glory
Eagle Armor+2 Morale and LuckArmorIIRare
Vortex RobeWhenever the opponent casts a spell, restores 40% of its cost as mana to the wearerRestores 60% of the spell’s cost50ArmorIIRareEthereal Knowledge
Plate Armor+3 Defense+2 more Defense50ArmorIIRareKnight's Honor
Tunic of the Cyclops King+4 Spell Power+2 more Spell Power. Can be upgraded an unlimited number of times. (+120)
LevelCostTotal
2120120
3240360
4360720
54801200
66001800
77202520
88403360
99604320
1010805400
ArmorIIRare
Robe of EnlightenmentMakes all tier-3 spells available to the heroArmorIIIEpic
Ribcage Armor+4 Defense, then +15% total Defense+2 more Defense75ArmorIIIEpicShadow of Death
Iridescent Cloak+6 Knowledge+3 more Knowledge75ArmorIIIEpicShamanic Soul
Armor of Wonder+2 Attack, Defense, Spell Power, and Knowledge+1 more to every attribute100ArmorIVLegendaryAngelic Alliance
Heartbeat+12 Spell Power, but –4 Knowledge+4 more Spell Power100ArmorIVLegendaryBeelzebub's Chosen
Dragon Scale Armor+4 Attack and Defense+1 to both attributes100ArmorIVLegendaryPower of the Dragon Father
Head
Head Torch+1 sight radiusHeadICommon
Fancy MaskGenerates 2 Focus Point(s) at the start of the battle+2 more Focus Point(s)25HeadICommonInner Song
Armet+2 Defense+1 more Defense25HeadICommonKnight's Honor
Straw Hat+1 Knowledge+1 more Knowledge25HeadICommonPauper's Glory
Crown of the Supreme Magi+4 Knowledge+2 more Knowledge. Can be upgraded an unlimited number of times. (+120)
LevelCostTotal
2120120
3240360
4360720
54801200
66001800
77202520
88403360
99604320
1010805400
HeadIIRare
Scholar's Tiara+2 Knowledge+1 more Knowledge50HeadIIRareScholar's Wisdom
Mirror TiaraRestores 8 mana to the hero each morning+8 more mana75HeadIIIEpicTranquil Glass
Helm of Heavenly Enlightenment+2 Attack, Defense, Spell Power, and Knowledge+1 more to every attribute100HeadIVLegendaryAngelic Alliance
Beelzebub's Boon+12 Knowledge, but –4 Spell Power+4 more Knowledge100HeadIVLegendaryBeelzebub's Chosen
Seven Eyes+1 level(s) to all spells while worn+1 more spell level(s)100HeadIVLegendaryEthereal Knowledge
Crown of Dragontooth+4 Spell Power and Knowledge+1 to both attributes100HeadIVLegendaryPower of the Dragon Father
Amelchia's Law–1 Initiative to enemy creatures–1 more Initiative100HeadIVLegendaryDominion of Puppeteers
Sirin MaskThe cooldowns of all spells are reduced by 1 round(s)–1 fewer round(s)100HeadIVLegendaryShamanic Soul
Spellbinder's HatMakes all tier-5 spells available to the heroHeadIVLegendary
Two-faced MaskThe hero can use the Spellbook 1 additional time(s) per battle roundHeadIVLegendary
Boots
Magic SlippersSpells cost 2 less mana1 less mana25BootsICommonEthereal Knowledge
Dragonbone Greaves+1 Spell Power and Knowledge+1 to both attributes25BootsICommonPower of the Dragon Father
Boots of Travel+10 Movement points+5 more Movement points50BootsIIRareWanderer's Way
Legion's Leg+50% to all creature growth in all your citiesBootsIIIEpic
Bone Boots+4 Spell Power, then +15% total Spell Power+2 more Spell Power75BootsIIIEpicShadow of Death
Swamp BootsEliminates all terrain penaltiesBootsIIIEpic
Sandals of the Saint+2 Attack, Defense, Spell Power, and Knowledge+1 more to every attribute100BootsIVLegendaryAngelic Alliance
Seven-League Boots+30 Movement pointsBootsIVLegendary
Warlord Boots+2 Initiative to friendly creaturesBootsIVLegendary
Belt
Rope Belt+1 Defense+1 more Defense25BeltICommonPauper's Glory
Soulless Sash–1 Morale to enemy creaturesBeltICommon
Flask of OblivionAll negative effects on friendly creatures last for –1 round(s)–1 fewer round(s)50BeltIIRareAnesthesia
Endless BagProduces +1 Gem, Crystal, or Mercury daily. (The resource depends on the bearer’s faction.)BeltIIRare
Warrior's Belt+2 Attack+1 more Attack50BeltIIRareWarrior's Strength
Protective Belt–10% Magic Damage taken. Friendly creatures are immune to the effects of “Sleep.”–5% less Magic Damage taken75BeltIIIEpicDwarven Fortitude
Skull of Milos+1000 Gold daily+1000 more Gold75BeltIIIEpicMilos's Curse
Slithering Sash+3 Spell Power and Knowledge+1 to both attributes75BeltIIIEpicPower of the Dragon Father
Celestial Sash of Bliss+2 Attack, Defense, Spell Power, and Knowledge+1 more to every attribute100BeltIVLegendaryAngelic Alliance
Magic Key RingNegates all effects that restrict creature rearrangement, surrendering, fleeing, and using the Spellbook and hero abilities in battleBeltIVLegendary
Spells in BottlesMakes all tier-4 spells available to the heroBeltIVLegendary
Back
Omencaller–1 to enemy LuckBackICommon
Automated Antimagic Shield–10% Magic Damage taken. Friendly creatures are immune to the effects of “Berserk.”–5% less Magic Damage taken50BackIIRareDwarven Fortitude
Monster HeadAll creatures always have neutral Morale in battleBackIIRare
Dragon Wing Tabard+2 Spell Power and Knowledge+1 to both attributes50BackIIRarePower of the Dragon Father
BackpackThe hero has +10 Movement points+5 more Movement points50BackIIRareWanderer's Way
Wizard's Cloak+2 Spell Power+1 more Spell Power50BackIIRareWizard's Might
Banner of Six Winds+1 Speed to friendly creaturesBackIIIEpic
Light-and-Shade CloakFriendly creatures can still use Ranged attacks while adjacent to enemy creaturesFriendly creatures deal +5% Ranged Damage75BackIIIEpicLiving Arrows
Second Shade+4 Knowledge, then +15% total Knowledge+2 more Knowledge75BackIIIEpicShadow of Death
Ambassador's Sash+10% to Persuasion Power in Diplomacy+5% more Persuasion Power in Diplomacy100BackIVLegendaryAmbassador's Word
Fallen Angel WingsGrants constant flightBackIVLegendary
Rings
Wooden Ring+1 Morale+1 more Morale25RingsICommonPauper's Glory
Quiet Eye of a Dragon+1 Attack and Defense+1 to both attributes25RingsICommonPower of the Dragon Father
Clenching RingAll effects applied by the hero last +1 round(s)+1 more round(s)25RingsICommonShamanic Soul
Chain LinkForbids the wearer and the enemy hero to cast tier-1 and 2 spells in combatRingsIIRare
Ring of LifeAll positive effects on friendly creatures last for +1 round(s)+1 more round(s)50RingsIIRareAnesthesia
Emerald Resonance Controller–10% to enemy hero Spell Power. Friendly creatures are immune to the effects that kill additional units–5% more50RingsIIRareDwarven Fortitude
Keeper's Ring+2 Defense+1 more Defense50RingsIIRareKeeper's Fortitude
Ring of NeutralityAll creatures always have neutral Luck in battleRingsIIRare
Sixth Finger–1 to enemy Morale and LuckRingsIIRare
Shard of SerenityRestores 6 mana to the hero each morning+6 more mana50RingsIIRareTranquil Glass
Tabar Signet-Ring+1 Luck+1 more Luck50RingsIIRareUkhtabar Signet
Brass KnucklesFriendly creatures take –30% counterattack Damage–15% less counterattack Damage75RingsIIIEpicDuelist's Pride
Crimson Resonance Controller–20% to enemy hero Spell Power. Friendly creatures are immune to effects that prevent them from attacking–10% more to enemy Spell Power75RingsIIIEpicDwarven Fortitude
Lord's RingFriendly creatures have +1 InitiativeRingsIIIEpic
Seal of Silence+1 round(s) to the cooldown of all enemy spellsRingsIIIEpic
Soulcaller RingThe enemy loses 1 levels on all Magic School spellsRingsIVLegendary
Item
Boreoloth's Tail+1 Defense+1 more Defense. Can be upgraded an unlimited number of times. (+30)
LevelCostTotal
23030
36090
490180
5120300
6150450
7180630
8210840
92401080
102701350
ItemICommon
Boreoloth's Hand+1 Attack+1 more Attack. Can be upgraded an unlimited number of times. (+30)
LevelCostTotal
23030
36090
490180
5120300
6150450
7180630
8210840
92401080
102701350
ItemICommon
Boreoloth's Head+1 Knowledge+1 more Knowledge. Can be upgraded an unlimited number of times. (+30)
LevelCostTotal
23030
36090
490180
5120300
6150450
7180630
8210840
92401080
102701350
ItemICommon
Boreoloth's Heart+1 Spell Power+1 more Spell Power. Can be upgraded an unlimited number of times. (+30)
LevelCostTotal
23030
36090
490180
5120300
6150450
7180630
8210840
92401080
102701350
ItemICommon
Music SheetFriendly creatures’ abilities deal +40% Damage+20% more Damage25ItemICommonInner Song
Magic ScrollAdds the “Impending Fate” spell to the hero’s SpellbookItemICommon
Golden Moth+250 Gold daily+250 more Gold25ItemICommonMilos's Curse
Last Coin+1 Luck+1 more Luck25ItemICommonPauper's Glory
Liquid Silence+1 round(s) to the cooldown of all enemy creature abilities+1 more round(s)25ItemICommonDominion of Puppeteers
Magic MirrorRestores 4 mana to the hero each morning+4 more mana25ItemICommonTranquil Glass
Ancient IdolThe hero ignores the restriction on using the same Magic School twiceItemIIRare
Deck of SpellsMakes all tier-1 spells available to the heroItemIIRare
Enchanted Magic ScrollAdds the max. level “Impending Fate” spell to the hero’s SpellbookItemIIRare
Keeper's Amulet+2 Defense+1 more Defense50ItemIIRareKeeper's Fortitude
Quivering QuiverFriendly creatures deal +10% Ranged Damage+5% more Ranged Damage50ItemIIRareLiving Arrows
Golden Pig+500 Gold daily+500 more Gold50ItemIIRareMilos's Curse
Orb of DestructionHeroic Strike deals +10 base DamageItemIIRare
Scholar's Amulet+2 Knowledge+1 more Knowledge50ItemIIRareScholar's Wisdom
Spyglass+2 sight radiusItemIIRare
Ukh Signet+1 Morale+1 more Morale50ItemIIRareUkhtabar Signet
Voodoo DollWhen the wearer is defeated, they can still be hired back in the TavernItemIIRare
Warrior's Amulet+2 Attack+1 more Attack50ItemIIRareWarrior's Strength
Wizard's Amulet+2 Spell Power+1 more Spell Power50ItemIIRareWizard's Might
Flag of TruceThe wearer keeps their army whenever they flee from battleItemIIIEpic
Golden Goose EggGrants a random wondrous reward after a random number of upgradesVaries (60)
LevelCostTotal
26060
360120
460180
560240
660300
760360
860420
960480
1060540
ItemIIIEpic
Drum of WarFriendly creatures deal +1 Damage+1 more Damage50ItemIIIEpicKnight's Honor
Mythic Magic ScrollAdds the “Town Portal” spell to the hero’s SpellbookItemIIIEpic
Orb of InhibitionOn the first round of the battle, heroes cannot use their Spellbook or active abilitiesItemIIIEpic
Orb of DawnAll of the hero’s Daylight spells have +1 level(s). Enemy hero has –1 Daylight spell levelItemIIIEpic
Orb of EternityAll of the hero’s Arcane spells have +1 level(s). Enemy hero has –1 Arcane spell levelItemIIIEpic
Orb of TwilightAll of the hero’s Nightshade spells have +1 level(s). Enemy hero has –1 Nightshade spell levelItemIIIEpic
Primordial OrbAll of the hero’s Primal spells have +1 level(s). Enemy hero has –1 Primal spell levelItemIIIEpic
Runestone ShardsMakes all tier-2 spells available to the heroItemIIIEpic
Shackles of WarPrevents surrender and flee in battle. (This affects both the wearer and their opponent.)ItemIIIEpic
Tactical GuideThe opponent cannot rearrange creatures before the start of battleItemIIIEpic
Diplomatic Gifts+10% to Persuasion Power in Diplomacy+5% more Persuasion Power in Diplomacy100ItemIVLegendaryAmbassador's Word
Daylight TomeAllows the hero to cast all Daylight spellsItemIVLegendary
Nightshade TomeAllows the hero to cast all Nightshade spellsItemIVLegendary
Primal TomeAllows the hero to cast all Primal spellsItemIVLegendary
Arcane TomeAllows the hero to cast all Arcane spellsItemIVLegendary
Demonic HeartReplaces all the hero’s spells with their Masterful versions (if they exist)ItemIVLegendary
Lifeblood FaeFriendly creatures have +2 HP+1 more HP75ItemIVLegendaryAnesthesia
Pole StarGlobal Map spells of the hero cost 0 manaItemIVLegendary
Relic
Holy Sigil of EridoreWhile worn, grants all unlearned Subskills of the following Skills: Logistics, Battle Magic, WisdomRelicIIIEpic
Holy Sigil of InsaraWhile worn, grants all unlearned Subskills of the following Skills: Leadership, Insight, Diplomacy, TacticsRelicIIIEpic
Holy Sigil of MeareaWhile worn, grants all unlearned Subskills of the following Skills: Luck, Economy, Daylight MagicRelicIIIEpic
Holy Sigil of QuixWhile worn, grants all unlearned Subskills of the following Skills: Arcane Magic, Scouting, Battlecraft, ThaumaturgyRelicIIIEpic
Holy Sigil of RophWhile worn, grants all unlearned Subskills of the following Skills: Righteousness, Necromancy, Murmuring, Triumvirate’s Strength, Abyssal Communion, Summon SwarmRelicIIIEpic
Holy Sigil of The Second ManWhile worn, grants all unlearned Subskills of the following Skills: Offense, Summon Avatar, Siegecraft, CombatRelicIIIEpic
Holy Sigil of The Seven MagiWhile worn, grants all unlearned Subskills of the following Skills: Sorcery, Nightshade Magic, ResistanceRelicIIIEpic
Holy Sigil of UurdtWhile worn, grants all unlearned Subskills of the following Skills: Defense, Primal Magic, RecruitmentRelicIIIEpic

-- 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 set_id = row.set_id
        local entry = hub[set_id]
        if not entry then
            entry = {}
            entry.id = set_id or ''
            entry.name = row.name or ''
            entry.nameEn = row.nameEn or ''
            entry.descriptions = {} -- will be filled below
            hub[set_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 renderSet(setId, setsHub)
    local set = setsHub[setId]
    if not set then return '' end
    return set.name
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, setsHub)
    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, renderSet(item.artifact_set_id, setsHub)):attr('data-sort-value', 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)

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

    -- 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, setsHub)

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

return p