Module:Sandbox/Aurelain/ArtifactsOverview: Difference between revisions

From Heroes of Might and Magic: Olden Era Official Wiki
No edit summary
No edit summary
Line 81: Line 81:
     local fields = {}
     local fields = {}
     table.insert(fields, 'A.id = id');
     table.insert(fields, 'A.id = id');
     table.insert(fields, 'A.slot = slot');
     --table.insert(fields, 'A.slot = slot');
     table.insert(fields, 'A.rarity = rarity');
     --table.insert(fields, 'A.rarity = rarity');
     table.insert(fields, 'A.artifact_set_id = artifact_set_id');
     --table.insert(fields, 'A.artifact_set_id = artifact_set_id');
     --table.insert(fields, 'A.goods_value = goods_value');
     --table.insert(fields, 'A.goods_value = goods_value');
     table.insert(fields, 'A.max_level = max_level');
     --table.insert(fields, 'A.max_level = max_level');
     table.insert(fields, 'A.cost_base = cost_base');
     --table.insert(fields, 'A.cost_base = cost_base');
     table.insert(fields, 'A.cost_per_level = cost_per_level');
     --table.insert(fields, 'A.cost_per_level = cost_per_level');
     table.insert(fields, 'T.language = language');
     table.insert(fields, 'T.language = language');
     table.insert(fields, 'T.name = name');
     table.insert(fields, 'T.name = name');
Line 96: Line 96:
     local where = {}
     local where = {}
     table.insert(where, 'A.id NOT LIKE "campaign_%"')
     table.insert(where, 'A.id NOT LIKE "campaign_%"')
    table.insert(where, 'T.type = "artifact"')
     table.insert(where, '(T.language = "en" OR T.language = "' .. lang .. '")')
     table.insert(where, '(T.language = "en" OR T.language = "' .. lang .. '")')



Revision as of 11:01, 29 June 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:

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

-- 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',
    unit_window_narrative = 'Description'
}

-- 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, 'A.slot = slot');
    --table.insert(fields, 'A.rarity = rarity');
    --table.insert(fields, 'A.artifact_set_id = artifact_set_id');
    --table.insert(fields, 'A.goods_value = goods_value');
    --table.insert(fields, 'A.max_level = max_level');
    --table.insert(fields, 'A.cost_base = cost_base');
    --table.insert(fields, 'A.cost_per_level = cost_per_level');
    table.insert(fields, 'T.language = language');
    table.insert(fields, 'T.name = name');
    table.insert(fields, 'T.description = description');
    table.insert(fields, 'T.type = type');

    -- 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 = {}
                entry.slot = row.slot or ''
                entry.rarity = row.rarity or ''
                entry.artifact_set_id = row.artifact_set_id or ''
                --entry.goods_value = row.goods_value or 0
                entry.max_level = row.max_level or 0
                entry.cost_base = row.cost_base or 0
                entry.cost_per_level = row.cost_per_level or 0
                hub[id] = entry
            end
            if row.language == lang then
                entry.name = row.name 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 ''
                entry.slot = row.slot or ''
                entry.rarity = row.rarity or ''
                entry.artifact_set_id = row.artifact_set_id or ''
                --entry.goods_value = row.goods_value or 0
                entry.max_level = row.max_level or 0
                entry.cost_base = row.cost_base or 0
                entry.cost_per_level = row.cost_per_level or 0
                hub[id] = entry
            end
            if row.language == lang then
                entry.name = row.name or ''
                if row.type == 'artifact' then
                    entry.description = row.description or ''
                end
                if row.type == 'artifact_upgrade' then
                    entry.upgrade = row.description or ''
                end
                if row.type == 'artifact_narrative' then
                    entry.narrative = row.description or ''
                end
            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

-- TABLE HEADER
------------------------------------------------------------------------------------------------------------------------
local function createHeader(htmlTable, words)
    local tr = htmlTable:tag('tr')
    --addTh(tr, 'id')
    addTh(tr, words.wiki_artifact):attr('colspan', 2)
    addTh(tr, words.unit_window_narrative)
    addTh(tr, 'upgrade')
    --addTh(tr, 'narrative')
    addTh(tr, 'slot')
    addTh(tr, 'rarity')
    addTh(tr, 'artifact_set_id')
    --addTh(tr, 'goods_value')
    addTh(tr, 'max_level')
    addTh(tr, 'cost_base')
    addTh(tr, 'cost_per_level')
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

-- TABLE BODY
------------------------------------------------------------------------------------------------------------------------
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)
        addTd(tr, item.upgrade)
        --addTd(tr, item.narrative)
        addTd(tr, item.slot)
        addTd(tr, item.rarity)
        addTd(tr, item.artifact_set_id)
        addTd(tr, item.goods_value)
        addTd(tr, item.max_level)
        addTd(tr, item.cost_base)
        addTd(tr, item.cost_per_level)
    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