Module:Sandbox/Aurelain/ArtifactsOverview: Difference between revisions
From Heroes of Might and Magic: Olden Era Official Wiki
No edit summary |
No edit summary |
||
| Line 670: | Line 670: | ||
function p.display(frame) | function p.display(frame) | ||
--do return dump(frame) end | --do return dump(frame) end | ||
do return dump(mw.title.getCurrentTitle()) end | |||
-- Args | -- Args | ||
Revision as of 07:29, 3 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:
Module:Sandbox/Aurelain/ArtifactsOverview
-- Usage: see Module:ArtifactsOverview/doc
local p = {}
local f = mw.ustring.format
local UtilHyper = require('Module:UtilHyper')
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',
wiki_ao_items = 'items:',
wiki_ao_toggle = 'Toggle narratives',
wiki_ao_no_upgrade = 'Cannot be upgraded.',
wiki_ao_no_set = 'Not part of a set.',
wiki_ao_back_to_top = 'back to top',
}
local ROMAN = { 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII' }
local ALL_QUOTES = [["„“”«»「」『』']]
-- Displays a variable
------------------------------------------------------------------------------------------------------------------------
local function dump(target)
return '<pre>' .. mw.dumpObject(target) .. '</pre>'
end
------------------------------------------------------------------------------------------------------------------------
local function cleanHtmlWhitespace(str)
local cleaned = str:gsub(">%s+", ">"):gsub("%s+<", "<")
return cleaned
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 A.id LIKE '%%_scroll_%%'
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(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 = {} -- only available for scrolls
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(results)
local hub = consolidateScrolls(results)
--do return hub end
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.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
entry.artifacts = {} -- will be filled later on
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 assignArtifactsToSet(setsHub, artifactsHub)
for id, artifact in pairs(artifactsHub) do
local set = setsHub[artifact.artifact_set_id]
if set then
table.insert(set.artifacts, id)
end
end
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(item, lang, hyper, frame)
local description = mw.ustring.gsub(item.description, '[.。]-$', '') -- remove the ending dot
description = UtilHyper.apply(description, hyper)
if item.spells then
local icons = {}
for _, id in ipairs(item.spells) do
table.insert(icons, frame:preprocess(f('{{LinkIcon|lang=%s|id=%s|type=spell}}', lang, id)))
end
local enumeration = table.concat(icons, '<br>')
local pattern = f('([%s]).-([%s])', ALL_QUOTES, ALL_QUOTES)
local button = f("<span class='toggle mw-customtoggle-%s' role='button'>…</span>", item.id)
description = mw.ustring.gsub(item.description, pattern, '%1' .. button .. '%2')
local markup = '<div id="mw-customcollapsible-%s" class="spells mw-collapsible mw-collapsed">%s</div>'
local zone = f(markup, item.id, enumeration)
return mw.text.trim(description) .. zone
end
return mw.text.trim(description)
end
------------------------------------------------------------------------------------------------------------------------
local function renderUpgrade(item, words, lang, frame)
if item.max_level < 2 then
return frame:preprocess(f('{{Hint|—|%s}}', words.wiki_ao_no_upgrade))
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
local hint = frame:preprocess(f('{{Hint|—|%s}}', words.wiki_ao_no_upgrade))
tr:tag('td'):wikitext(hint):attr('data-sort-value', 999999)
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 renderSetFloating(set, words)
local lines = {}
table.insert(lines, f('<div class{{=}}"set-title">%s</div>', set.name))
for _, entry in ipairs(set.descriptions) do
table.insert(lines, f('<div class{{=}}"set-level">%s %s</div>', entry.required_amount, words.wiki_ao_items))
table.insert(lines, f('<div class{{=}}"set-text">%s</div>', entry.description))
end
return table.concat(lines, '')
end
------------------------------------------------------------------------------------------------------------------------
local function renderSetName(set, lang, words, frame)
local url = getUrl(set.nameEn, lang)
local text = f('[[%s|%s]]', url, set.name)
local floating = renderSetFloating(set, words)
return frame:preprocess(f('{{Hint|%s|%s|side=left|class=set-floating}}', text, floating))
end
------------------------------------------------------------------------------------------------------------------------
local function renderSetIcon(artifact, lang, frame)
local url = getUrl(artifact.nameEn, lang)
local file = f('[[File:%s.png{{!}}32px{{!}}link{{=}}%s]]', artifact.id, url)
local floating = f('<div class{{=}}"set-title">%s</div>%s', artifact.name, artifact.description)
return frame:preprocess(f('{{Hint|%s|%s|style=pointer-events:none}}', file, floating))
end
------------------------------------------------------------------------------------------------------------------------
local function renderSet(setId, setsHub, artifactsHub, lang, words, frame)
local set = setsHub[setId]
if not set then
return frame:preprocess(f('{{Hint|—|%s}}', words.wiki_ao_no_set))
end
local icons = {}
for _, id in ipairs(set.artifacts) do
local artifact = artifactsHub[id]
table.insert(icons, renderSetIcon(artifact, lang, frame))
end
return renderSetName(set, lang, words, frame) .. '<br>' .. table.concat(icons, '')
end
------------------------------------------------------------------------------------------------------------------------
local function renderSlotSeparator(slot, words)
local file = f('[[File:%s|64px|link=]]', SLOT_ICONS[slot])
local text = words['wiki_ao_' .. slot]
local topHash = mw.title.getCurrentTitle() .. '#'
local back = f('<span class="back-to-top">[[%s|%s]]</span>', topHash, words.wiki_ao_back_to_top)
return '<div id="' .. text:gsub(' ', '_') .. '">' .. file .. text .. back .. '</div>'
end
------------------------------------------------------------------------------------------------------------------------
local function getDsv(value)
return value == '' and 'ZZZZZZ' or value
end
------------------------------------------------------------------------------------------------------------------------
local function addNarrative(htmlTable, narrative)
local markup = cleanHtmlWhitespace([[
<tr class='expand-child mw-collapsible mw-collapsed' id='mw-customcollapsible-narrative'>
<td colspan='100' class='narrative-full-td'>
%s
</td>
</tr>
]])
htmlTable:node(f(markup, narrative))
end
-- TABLE BODY
------------------------------------------------------------------------------------------------------------------------
local function createBody(htmlTable, list, lang, words, frame, setsHub, artifactsHub, hyper)
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, lang, hyper, frame))
local upgradeDescription = renderUpgrade(item, words, lang, frame)
addTd(tr, upgradeDescription):attr('data-sort-value', getDsv(upgradeDescription))
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])
local set = renderSet(item.artifact_set_id, setsHub, artifactsHub, lang, words, frame)
addTd(tr, set):attr('data-sort-value', getDsv(item.artifact_set_id))
addNarrative(htmlTable, item.narrative)
end
end
------------------------------------------------------------------------------------------------------------------------
local function createToggleButton(words)
return f("<span role='button' class='toggle mw-customtoggle-narrative'>%s</span>", words.wiki_ao_toggle)
end
------------------------------------------------------------------------------------------------------------------------
local function createNav(words)
local icons = {}
local ordered = {}
for slot_name, order_index in pairs(SLOT_ORDER) do
ordered[order_index] = slot_name
end
for _, slot in ipairs(ordered) do
local name = words['wiki_ao_' .. slot]
local hash = '#' .. name:gsub(' ', '_')
local markup = '<span class="jump">[[File:%s|32px|link=%s]][[%s|%s]]</span>';
local icon = f(markup, SLOT_ICONS[slot], hash, hash, name)
table.insert(icons, icon)
end
local toc = table.concat(icons, ' ')
local toggle = createToggleButton(words)
local div = mw.html.create('div'):addClass('nav'):attr('id', 'artifacts-nav')
div:wikitext(toggle .. toc)
return tostring(div)
end
-- M A I N
------------------------------------------------------------------------------------------------------------------------
function p.display(frame)
--do return dump(frame) end
do return dump(mw.title.getCurrentTitle()) end
-- Args
local args = frame.args
local lang = args.lang ~= '' and args.lang or 'en'
--lang = 'ja'
local hyper = args.hyper
--hyper = 'Sorcery=Sorcery; Nightshade [Ss]pells=Nightshade_Spells'
-- Language
local words = translateIds(TRANSLATION_IDS, lang)
-- Cargo
local results = queryMain(lang)
local hub = consolidateMain(results)
--do return dump(hub) end
local list = flatten(hub);
if #list == 0 then return words.wiki_none end
local setsResults = querySets(lang)
local setsHub = consolidateSets(setsResults)
assignArtifactsToSet(setsHub, hub)
-- Various manipulations
table.sort(list, sortList)
-- Table
local htmlTable = mw.html.create('table')
htmlTable:addClass('wikitable sortable table-nobands')
htmlTable:attr('id', words['wiki_ao_left_hand']:gsub(' ', '_'))
createHeader(htmlTable, words)
createBody(htmlTable, list, lang, words, frame, setsHub, hub, hyper)
-- Output
local nav = createNav(words)
local styleTag = frame:extensionTag('templatestyles', '', { src = frame:getTitle() .. '/styles.css' })
return '<div class="artifacts-overview">' .. styleTag .. nav .. tostring(htmlTable) .. '</div>'
end
return p


