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:

Artifact🗯️description
Ambassador's Sash+10% to Persuasion Power in Diplomacy.
Diplomatic Gifts+10% to Persuasion Power in Diplomacy.
Ancient IdolThe hero ignores the restriction on using the same Magic School twice.
Armor of Wonder+2 Attack, Defense, Spell Power, and Knowledge.
Celestial Sash of Bliss+2 Attack, Defense, Spell Power, and Knowledge.
Helm of Heavenly Enlightenment+2 Attack, Defense, Spell Power, and Knowledge.
Lion's Shield of Courage+2 Attack, Defense, Spell Power, and Knowledge.
Sandals of the Saint+2 Attack, Defense, Spell Power, and Knowledge.
Sword of Judgement+2 Attack, Defense, Spell Power, and Knowledge.
Banner of Six Winds+1 Speed to friendly creatures.
Chitinous Shield+12 Defense, but –4 Attack.
Demon Claw+12 Attack, but –4 Defense.
Beelzebub's Boon+12 Knowledge, but –4 Spell Power.
Heartbeat+12 Spell Power, but –4 Knowledge.
Boreoloth's Tail+1 Defense.
Boreoloth's Hand+1 Attack.
Boreoloth's Head+1 Knowledge.
Boreoloth's Heart+1 Spell Power.
CaduceusHeroic Strike restores 4 mana.
Deck of SpellsMakes all tier-1 spells available to the hero.
Daylight TomeAllows the hero to cast all Daylight spells.
Nightshade TomeAllows the hero to cast all Nightshade spells.
Primal TomeAllows the hero to cast all Primal spells.
Arcane TomeAllows the hero to cast all Arcane spells.
Chain LinkForbids the wearer and the enemy hero to cast tier-1 and 2 spells in combat.
Chain Mail+3 Defense.
Robe of EnlightenmentMakes all tier-3 spells available to the hero.
Crown of the Supreme Magi+4 Knowledge.
Demonic HeartReplaces all the hero’s spells with their Masterful versions (if they exist).
Brass KnucklesFriendly creatures take –30% counterattack Damage.
BucklerFriendly creatures can counterattack even if the attacking enemy has “Swift Strike.”
RapierFriendly creatures deal +10% Melee Damage
Eagle Armor+2 Morale and Luck.
Flask of OblivionAll negative effects on friendly creatures last for –1 round(s).
Lifeblood FaeFriendly creatures have +2 HP.
Ring of LifeAll positive effects on friendly creatures last for +1 round(s).
Enchanted Magic ScrollAdds the max. level “Impending Fate” spell to the hero’s Spellbook.
Endless BagProduces +1 Gem, Crystal, or Mercury daily. (The resource depends on the bearer’s faction.)
Crescent DaggerSpells and creature abilities ignore 40% of enemy magic resistance.
Magic SlippersSpells cost 2 less mana.
Seven Eyes+1 level(s) to all spells while worn.
Vortex RobeWhenever the opponent casts a spell, restores 40% of its cost as mana to the wearer.
ExcaliburHeroic Strike kills +1 unit(s). Works on creatures up to tier 7.
Fallen Angel WingsGrants constant flight.
Fine Wand+1 level to all spells.
Flag of TruceThe wearer keeps their army whenever they flee from battle.
Garotte–1 Speed to all enemies.
Automated Antimagic Shield–10% Magic Damage taken. Friendly creatures are immune to the effects of “Berserk.”
Crimson Resonance Controller–20% to enemy hero Spell Power. Friendly creatures are immune to effects that prevent them from attacking.
Emerald Resonance Controller–10% to enemy hero Spell Power. Friendly creatures are immune to the effects that kill additional units.
Protective Belt–10% Magic Damage taken. Friendly creatures are immune to the effects of “Sleep.”
Golden Goose EggGrants a random wondrous reward after a random number of upgrades.
Head Torch+1 sight radius.
Holy Sigil of EridoreWhile worn, grants all unlearned Subskills of the following Skills: Logistics, Battle Magic, Wisdom.
Holy Sigil of InsaraWhile worn, grants all unlearned Subskills of the following Skills: Leadership, Insight, Diplomacy, Tactics.
Holy Sigil of MeareaWhile worn, grants all unlearned Subskills of the following Skills: Luck, Economy, Daylight Magic.
Holy Sigil of QuixWhile worn, grants all unlearned Subskills of the following Skills: Arcane Magic, Scouting, Battlecraft, Thaumaturgy.
Holy Sigil of RophWhile worn, grants all unlearned Subskills of the following Skills: Righteousness, Necromancy, Murmuring, Triumvirate’s Strength, Abyssal Communion, Summon Swarm.
Holy Sigil of The Second ManWhile worn, grants all unlearned Subskills of the following Skills: Offense, Summon Avatar, Siegecraft, Combat.
Holy Sigil of The Seven MagiWhile worn, grants all unlearned Subskills of the following Skills: Sorcery, Nightshade Magic, Resistance.
Holy Sigil of UurdtWhile worn, grants all unlearned Subskills of the following Skills: Defense, Primal Magic, Recruitment.
Pelagic HourglassThe cooldowns of all spells are reduced by 1 round(s).
Fancy MaskGenerates 2 Focus Point(s) at the start of the battle.
Music SheetFriendly creatures’ abilities deal +40% Damage.
Singing Pan-PipeAll effects applied by friendly creatures last +1 round(s).
Keeper's Amulet+2 Defense.
Keeper's Ring+2 Defense.
Armet+2 Defense.
Drum of WarFriendly creatures deal +1 Damage.
Lance+3 Attack.
Misericorde+2 Attack.
Plate Armor+3 Defense.
Legion's Leg+50% to all creature growth in all your cities.
Light-and-Shade CloakFriendly creatures can still use Ranged attacks while adjacent to enemy creatures.
Quivering QuiverFriendly creatures deal +10% Ranged Damage.
Shroomwood BowEliminates Ranged Damage penalty for friendly creatures.
Lord's RingFriendly creatures have +1 Initiative.
Magic Key RingNegates all effects that restrict creature rearrangement, surrendering, fleeing, and using the Spellbook and hero abilities in battle.
Magic ScrollAdds the “Impending Fate” spell to the hero’s Spellbook.
Golden Moth+250 Gold daily.
Golden Pig+500 Gold daily.
Skull of Milos+1000 Gold daily.
Monster HeadAll creatures always have neutral Morale in battle.
Mythic Magic ScrollAdds the “Town Portal” spell to the hero’s Spellbook.
Ogre's Club of Havoc+4 Attack.
Omencaller–1 to enemy Luck.
Orb of DestructionHeroic Strike deals +10 base Damage.
Orb of InhibitionOn the first round of the battle, heroes cannot use their Spellbook or active abilities.
Fish-to-the-Face+1 Attack.
Last Coin+1 Luck.
Rags+1 Spell Power.
Rope Belt+1 Defense.
Straw Hat+1 Knowledge.
Wooden Ring+1 Morale.
Pole StarGlobal Map spells of the hero cost 0 mana.
Crown of Dragontooth+4 Spell Power and Knowledge.
Dragon Scale Armor+4 Attack and Defense.
Dragon Scale Shield+3 Attack and Defense.
Dragon Wing Tabard+2 Spell Power and Knowledge.
Dragonbone Greaves+1 Spell Power and Knowledge.
Quiet Eye of a Dragon+1 Attack and Defense.
Red Dragon Flame Tongue+2 Attack and Defense.
Slithering Sash+3 Spell Power and Knowledge.
Orb of DawnAll of the hero’s Daylight spells have +1 level(s). Enemy hero has –1 Daylight spell level.
Orb of EternityAll of the hero’s Arcane spells have +1 level(s). Enemy hero has –1 Arcane spell level.
Orb of TwilightAll of the hero’s Nightshade spells have +1 level(s). Enemy hero has –1 Nightshade spell level.
Primordial OrbAll of the hero’s Primal spells have +1 level(s). Enemy hero has –1 Primal spell level.
Ring of NeutralityAll creatures always have neutral Luck in battle.
Liquid Silence+1 round(s) to the cooldown of all enemy creature abilities.
Amelchia's Law–1 Initiative to enemy creatures.
Truthmaker–10% Attack to enemy creatures.
Truthseeker–10% Defense to enemy creatures.
Runestone ShardsMakes all tier-2 spells available to the hero.
Scholar's Amulet+2 Knowledge.
Scholar's Tiara+2 Knowledge.
Seal of Silence+1 round(s) to the cooldown of all enemy spells.
Seven-League Boots+30 Movement points.
Shackles of WarPrevents surrender and flee in battle. (This affects both the wearer and their opponent.)
Bone Boots+4 Spell Power, then +15% total Spell Power.
Ribcage Armor+4 Defense, then +15% total Defense.
Dark Hatchet+4 Attack, then +15% total Attack.
Second Shade+4 Knowledge, then +15% total Knowledge.
Clenching RingAll effects applied by the hero last +1 round(s).
Sirin MaskThe cooldowns of all spells are reduced by 1 round(s).
Iridescent Cloak+6 Knowledge.
Shaman Staff+6 Spell Power.
Shoddy Shield+2 Defense.
Sixth Finger–1 to enemy Morale and Luck.
Soulless Sash–1 Morale to enemy creatures.
Soulcaller RingThe enemy loses 1 levels on all Magic School spells.
Spellbinder's HatMakes all tier-5 spells available to the hero.
Spells in BottlesMakes all tier-4 spells available to the hero.
Spyglass+2 sight radius.
Swamp BootsEliminates all terrain penalties.
Tactical GuideThe opponent cannot rearrange creatures before the start of battle.
Shield of the Rampaging Ogre+4 Defense.
Mirror TiaraRestores 8 mana to the hero each morning.
Magic MirrorRestores 4 mana to the hero each morning.
Shard of SerenityRestores 6 mana to the hero each morning.
Tunic of the Cyclops King+4 Spell Power.
Two-faced MaskThe hero can use the Spellbook 1 additional time(s) per battle round.
Tabar Signet-Ring+1 Luck.
Ukh Signet+1 Morale.
Voodoo DollWhen the wearer is defeated, they can still be hired back in the Tavern.
BackpackThe hero has +10 Movement points.
Boots of Travel+10 Movement points.
Warlord Boots+2 Initiative to friendly creatures.
Warrior's Belt+2 Attack.
Warrior's Amulet+2 Attack.
Wizard's Cloak+2 Spell Power.
Wizard's Amulet+2 Spell Power.

-- Usage: {{#invoke:ArtifactsOverview|display | lang=en}}
local p = {}
local f = mw.ustring.format

-- Note: The right side is just a fallback, it will seldom be used.
local TRANSLATION_IDS = {
    wiki_none = 'None',
    wiki_artifact = 'Artifact'
}

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

-- Interrogates the main Cargo table
------------------------------------------------------------------------------------------------------------------------
local function queryMain(lang)
    -- fields:
    local fields = {}
    table.insert(fields, 'A.id = id');
    table.insert(fields, 'T.language = language');
    table.insert(fields, 'T.name = name');
    table.insert(fields, 'T.description = description');

    -- where:
    local where = {}
    table.insert(where, 'A.id NOT LIKE "campaign_%"')
    table.insert(where, 'T.type = "artifact"')
    table.insert(where, '(T.language = "en" OR T.language = "' .. lang .. '")')

    -- query:
    return mw.ext.cargo.query('Artifact=A, Translation=T', table.concat(fields, ','), {
        join = 'A.id = T.target_id',
        where = table.concat(where, ' AND '),
        limit = 500
    })
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.spells = {}
                hub[id] = entry
            end
            if row.language == lang then
                entry.name = row.name or ''
                entry.description = row.description or ''
            end
            if row.language == 'en' then
                entry.nameEn = row.name or ''
                local spellId = row.id:gsub('.*artifact_', '')
                table.insert(entry.spells, spellId)
            end
        end
    end
    return hub
end

-- Folds all Cargo results into a single dictionary:
------------------------------------------------------------------------------------------------------------------------
local function consolidate(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 ''
                hub[id] = entry
            end
            if row.language == lang then
                entry.name = row.name or ''
                entry.description = row.description or ''
            end
            if row.language == 'en' then
                entry.nameEn = row.name or ''
            end
        end
    end
    return hub
end

------------------------------------------------------------------------------------------------------------------------
local function sortList(a, b)
    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

------------------------------------------------------------------------------------------------------------------------
local function createHeader(htmlTable, words)
    local tr = htmlTable:tag('tr')
    --addTh(tr, 'id')
    addTh(tr, words.wiki_artifact):attr('colspan', 2)
    addTh(tr, 'description'):attr('colspan', 2)
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 createBody(htmlTable, list, lang)
    local currentType = list[1].param1
    local currentFaction = list[1].faction
    for _, item in ipairs(list) do
        -- separators
        if item.faction ~= currentFaction then
            currentFaction = item.faction
            currentType = item.class_type
            addSeparator(htmlTable, 'separator-large', '')
        elseif item.class_type ~= currentType then
            currentType = item.class_type
            addSeparator(htmlTable, 'separator-tiny', '')
        end

        local tr = htmlTable:tag('tr'):addClass('artifact')
        --addTd(tr, item.id)
        addTd(tr, renderIcon(item.id, item.nameEn, lang))
        addTd(tr, renderName(item.name, item.nameEn, lang))
        addTd(tr, item.description)
    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 = 'ja'

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

    -- Cargo
    local results = queryMain(lang)
    local hub = consolidate(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')
    createHeader(htmlTable, words)
    createBody(htmlTable, list, lang)

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

return p