<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?action=history&amp;feed=atom&amp;title=Module%3ASandbox%2FGreenStickInsect%2FHeroesOverview</id>
	<title>Module:Sandbox/GreenStickInsect/HeroesOverview - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?action=history&amp;feed=atom&amp;title=Module%3ASandbox%2FGreenStickInsect%2FHeroesOverview"/>
	<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/GreenStickInsect/HeroesOverview&amp;action=history"/>
	<updated>2026-10-20T04:25:17Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.44.5</generator>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/GreenStickInsect/HeroesOverview&amp;diff=28152&amp;oldid=prev</id>
		<title>76561199054722510: Create sandbox to test something</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/GreenStickInsect/HeroesOverview&amp;diff=28152&amp;oldid=prev"/>
		<updated>2026-07-10T11:14:51Z</updated>

		<summary type="html">&lt;p&gt;Create sandbox to test something&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- Usage: {{#invoke:HeroesOverview|display|lang=en|faction=Temple}}&lt;br /&gt;
local p = {}&lt;br /&gt;
local f = mw.ustring.format&lt;br /&gt;
local SkillsOverview = require(&amp;quot;Module:SkillsOverview&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local FACTION_ORDER = {&lt;br /&gt;
    human = 1,&lt;br /&gt;
    undead = 2,&lt;br /&gt;
    nature = 3,&lt;br /&gt;
    demon = 4,&lt;br /&gt;
    unfrozen = 5,&lt;br /&gt;
    dungeon = 6,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MIGHT_MAGIC_ORDER = {&lt;br /&gt;
    might = 1,&lt;br /&gt;
    magic = 2,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &amp;#039;human&amp;#039;,&lt;br /&gt;
    Necropolis = &amp;#039;undead&amp;#039;,&lt;br /&gt;
    Grove = &amp;#039;nature&amp;#039;,&lt;br /&gt;
    Hive = &amp;#039;demon&amp;#039;,&lt;br /&gt;
    Schism = &amp;#039;unfrozen&amp;#039;,&lt;br /&gt;
    Dungeon = &amp;#039;dungeon&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local TYPE_ICONS = {&lt;br /&gt;
    might = &amp;#039;Wip_Might_Specialization_Icon&amp;#039;,&lt;br /&gt;
    magic = &amp;#039;Wip_Mage_Specialization_Icon&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Note: The right side is just a fallback, it will seldom be used.&lt;br /&gt;
local TRANSLATION_IDS = {&lt;br /&gt;
    wiki_heroes_hero = &amp;#039;Hero&amp;#039;,&lt;br /&gt;
    wiki_heroes_type = &amp;#039;Type&amp;#039;,&lt;br /&gt;
    wiki_heroes_class = &amp;#039;Class&amp;#039;,&lt;br /&gt;
    wiki_heroes_specialty = &amp;#039;Specialty&amp;#039;,&lt;br /&gt;
    wiki_might = &amp;#039;Might&amp;#039;,&lt;br /&gt;
    wiki_magic = &amp;#039;Magic&amp;#039;,&lt;br /&gt;
    starting_skills = &amp;#039;Starting Skills&amp;#039;,&lt;br /&gt;
    starting_spells = &amp;#039;Starting Spells&amp;#039;,&lt;br /&gt;
    wiki_heroes_toggle = &amp;#039;Toggle specialty&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local SPEC_ICON_REPLACEMENTS = {&lt;br /&gt;
    Human = &amp;#039;Temple&amp;#039;,&lt;br /&gt;
    Necro = &amp;#039;Necropolis&amp;#039;,&lt;br /&gt;
    Nature = &amp;#039;Grove&amp;#039;,&lt;br /&gt;
    Demon = &amp;#039;Hive&amp;#039;,&lt;br /&gt;
    Unfrozen = &amp;#039;Schism&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local PATCH_NAME = {&lt;br /&gt;
    [&amp;#039;Summon Avatar&amp;#039;] = &amp;#039;Summon Avatar (Skill)&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &amp;#039;&amp;lt;pre&amp;gt;&amp;#039; .. mw.dumpObject(target) .. &amp;#039;&amp;lt;/pre&amp;gt;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Spawns a safe robotic url from an English name, fit for adding it before the visible name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function getUrl(nameEn, lang)&lt;br /&gt;
    if not nameEn then&lt;br /&gt;
        return &amp;#039;&amp;#039;&lt;br /&gt;
    end&lt;br /&gt;
    local url = PATCH_NAME[nameEn] or nameEn&lt;br /&gt;
    if lang ~= &amp;#039;en&amp;#039; then&lt;br /&gt;
        url = url .. &amp;#039;/&amp;#039; .. lang&lt;br /&gt;
    end&lt;br /&gt;
    return url&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Parses the Specialty Summaries from the &amp;quot;Heroes Overview&amp;quot; page&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function getSpecialtySummaries(lang)&lt;br /&gt;
    local suffix = lang == &amp;#039;en&amp;#039; and &amp;#039;&amp;#039; or &amp;#039;/&amp;#039; .. lang&lt;br /&gt;
    local targetTitle = mw.title.new(&amp;#039;Heroes Overview&amp;#039; .. suffix)&lt;br /&gt;
    local content = targetTitle and targetTitle.exists and targetTitle:getContent()&lt;br /&gt;
    local dictionary = {}&lt;br /&gt;
    for line in string.gmatch(content, &amp;quot;[^\r\n]+&amp;quot;) do&lt;br /&gt;
        local key, value = string.match(line, &amp;quot;%((.-)%)%s*=%s*(.*)&amp;quot;)&lt;br /&gt;
        if key and value then&lt;br /&gt;
            dictionary[key] = value&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return dictionary&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function convertToTitleCase(str)&lt;br /&gt;
    local titleCased = str:lower():gsub(&amp;quot;%f[%a]%a&amp;quot;, string.upper)&lt;br /&gt;
    return titleCased&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function cleanHtmlWhitespace(str)&lt;br /&gt;
    local cleaned = str:gsub(&amp;quot;&amp;gt;%s+&amp;quot;, &amp;quot;&amp;gt;&amp;quot;):gsub(&amp;quot;%s+&amp;lt;&amp;quot;, &amp;quot;&amp;lt;&amp;quot;)&lt;br /&gt;
    return cleaned&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function shortenSpecId(specId)&lt;br /&gt;
    return specId:gsub(&amp;#039;_specialization&amp;#039;, &amp;#039;&amp;#039;):gsub(&amp;#039;_hero_&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the text for some specific ids from Cargo Translations.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function translateIds(ids, lang, extra)&lt;br /&gt;
    -- Key list&lt;br /&gt;
    local list = {}&lt;br /&gt;
    for key, _ in pairs(ids) do&lt;br /&gt;
        list[#list + 1] = key&lt;br /&gt;
    end&lt;br /&gt;
    local idListString = &amp;#039;&amp;quot;&amp;#039; .. table.concat(list, &amp;#039;&amp;quot;, &amp;quot;&amp;#039;) .. &amp;#039;&amp;quot;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &amp;#039;target_id IN (&amp;#039; .. idListString .. &amp;#039;)&amp;#039;)&lt;br /&gt;
    table.insert(where, &amp;#039;language = &amp;quot;&amp;#039; .. lang .. &amp;#039;&amp;quot;&amp;#039;)&lt;br /&gt;
    if extra then&lt;br /&gt;
        table.insert(where, extra)&lt;br /&gt;
    end&lt;br /&gt;
    local results = mw.ext.cargo.query(&amp;#039;Translation&amp;#039;, &amp;#039;target_id, name&amp;#039;, {&lt;br /&gt;
        where = table.concat(where, &amp;#039; AND &amp;#039;),&lt;br /&gt;
        limit = 100&lt;br /&gt;
    })&lt;br /&gt;
&lt;br /&gt;
    -- Dictionary&lt;br /&gt;
    local dictionary = mw.clone(ids)&lt;br /&gt;
    for key, value in pairs(dictionary) do&lt;br /&gt;
        dictionary[key] = value .. &amp;#039;[[Data:WikiTranslations/&amp;#039; .. lang .. &amp;#039;#&amp;#039; .. key .. &amp;#039;|💬]]&amp;#039;&lt;br /&gt;
        --dictionary[key] = value&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&amp;#039;name&amp;#039;]&lt;br /&gt;
        if name and name ~= &amp;#039;&amp;#039; and not name:find(&amp;#039;💬&amp;#039;) then&lt;br /&gt;
            dictionary[row[&amp;#039;target_id&amp;#039;]] = name&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return dictionary&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Converts a dictionary into a linear array&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function flatten(hub)&lt;br /&gt;
    local list = {}&lt;br /&gt;
    for _, entry in pairs(hub) do&lt;br /&gt;
        list[#list + 1] = entry&lt;br /&gt;
    end&lt;br /&gt;
    return list&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Interrogates the Hero Cargo table&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function queryHero(lang, forcedFaction, forcedClass, forcedHeroes)&lt;br /&gt;
    -- fields:&lt;br /&gt;
    local fields = {}&lt;br /&gt;
    table.insert(fields, &amp;#039;Hero.id = id&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Hero.class_id = class_id&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Hero.faction = faction&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Hero.class_type = class_type&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Hero.icon = icon&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Hero.specialization_id = specialization_id&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Hero.start_skills = start_skills&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Hero.start_skill_levels = start_skill_levels&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Hero.start_magics = start_magics&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Translation.name = name&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Translation.language = language&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
    -- where:&lt;br /&gt;
    local where = {}&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        table.insert(where, &amp;#039;Hero.faction=&amp;quot;&amp;#039; .. forcedFaction .. &amp;#039;&amp;quot;&amp;#039;)&lt;br /&gt;
    end&lt;br /&gt;
    if forcedClass then&lt;br /&gt;
        table.insert(where, &amp;#039;Hero.class_id=&amp;quot;&amp;#039; .. forcedClass .. &amp;#039;&amp;quot;&amp;#039;)&lt;br /&gt;
    end&lt;br /&gt;
    if forcedHeroes then&lt;br /&gt;
        table.insert(where, &amp;#039;Hero.id IN (&amp;quot;&amp;#039; .. forcedHeroes:gsub(&amp;#039;,&amp;#039;, &amp;#039;&amp;quot;, &amp;quot;&amp;#039;) .. &amp;#039;&amp;quot;)&amp;#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &amp;#039;Hero.id not like &amp;quot;campaign%&amp;quot;&amp;#039;)&lt;br /&gt;
    table.insert(where, &amp;#039;Hero.id not like &amp;quot;tutorial%&amp;quot;&amp;#039;)&lt;br /&gt;
    table.insert(where, &amp;#039;Hero.id not like &amp;quot;%_fun_%&amp;quot;&amp;#039;)&lt;br /&gt;
    table.insert(where, &amp;#039;Translation.type = &amp;quot;hero&amp;quot;&amp;#039;)&lt;br /&gt;
    table.insert(where, &amp;#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&amp;#039; .. lang .. &amp;#039;&amp;quot;)&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
    -- query:&lt;br /&gt;
    return mw.ext.cargo.query(&amp;#039;Hero, Translation&amp;#039;, table.concat(fields, &amp;#039;,&amp;#039;), {&lt;br /&gt;
        join = &amp;#039;Hero.id = Translation.target_id&amp;#039;,&lt;br /&gt;
        where = table.concat(where, &amp;#039; AND &amp;#039;),&lt;br /&gt;
        limit = 500&lt;br /&gt;
    })&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Interrogates the HeroSpecialization Cargo table&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function queryHeroSpecialization(lang)&lt;br /&gt;
    -- fields:&lt;br /&gt;
    local fields = {}&lt;br /&gt;
    table.insert(fields, &amp;#039;HeroSpecialization.id = id&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;HeroSpecialization.icon = icon&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Translation.name = name&amp;#039;);&lt;br /&gt;
    table.insert(fields, &amp;#039;Translation.description = description&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
    -- where:&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &amp;#039;Translation.language = &amp;quot;&amp;#039; .. lang .. &amp;#039;&amp;quot;&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
    -- query:&lt;br /&gt;
    return mw.ext.cargo.query(&amp;#039;HeroSpecialization, Translation&amp;#039;, table.concat(fields, &amp;#039;,&amp;#039;), {&lt;br /&gt;
        join = &amp;#039;HeroSpecialization.id = Translation.target_id&amp;#039;,&lt;br /&gt;
        where = table.concat(where, &amp;#039; AND &amp;#039;),&lt;br /&gt;
        limit = 500&lt;br /&gt;
    })&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Folds all Cargo results into a single dictionary:&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateHeroes(lang, heroesResults, specializations, summaries)&lt;br /&gt;
    -- specializations&lt;br /&gt;
    local specHub = {}&lt;br /&gt;
    for _, row in ipairs(specializations) do&lt;br /&gt;
        specHub[row.id] = {&lt;br /&gt;
            icon = row.icon or &amp;#039;&amp;#039;,&lt;br /&gt;
            name = row.name or &amp;#039;&amp;#039;,&lt;br /&gt;
            description = row.description or &amp;#039;&amp;#039;,&lt;br /&gt;
        }&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- heroes&lt;br /&gt;
    local hub = {}&lt;br /&gt;
    for _, row in ipairs(heroesResults) do&lt;br /&gt;
        local id = row.id&lt;br /&gt;
        local entry = hub[id]&lt;br /&gt;
        if not entry then&lt;br /&gt;
            entry = {}&lt;br /&gt;
            entry.id = id or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.class_id = row.class_id or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.faction = row.faction or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.class_type = row.class_type or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.icon = row.icon or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.start_skills = row.start_skills or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.start_skill_levels = row.start_skill_levels or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.start_magics = row.start_magics or &amp;#039;&amp;#039;&lt;br /&gt;
            local spec = specHub[row.specialization_id] or {}&lt;br /&gt;
            entry.specId = row.specialization_id or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.specIcon = spec.icon or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.specName = spec.name or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.specDescription = spec.description or &amp;#039;&amp;#039;&lt;br /&gt;
            entry.specSummary = summaries[row.specialization_id] or &amp;#039;&amp;#039;&lt;br /&gt;
            hub[id] = entry&lt;br /&gt;
        end&lt;br /&gt;
        if row.language == lang then&lt;br /&gt;
            entry.name = row.name or &amp;#039;&amp;#039;&lt;br /&gt;
        end&lt;br /&gt;
        if row.language == &amp;#039;en&amp;#039; then&lt;br /&gt;
            entry.nameEn = row.name or &amp;#039;&amp;#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return hub&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortHeroes(a, b)&lt;br /&gt;
    local factionA = FACTION_ORDER[a.faction] or 99&lt;br /&gt;
    local factionB = FACTION_ORDER[b.faction] or 99&lt;br /&gt;
    if factionA ~= factionB then&lt;br /&gt;
        return factionA &amp;lt; factionB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local mmA = MIGHT_MAGIC_ORDER[a.class_type] or 99&lt;br /&gt;
    local mmB = MIGHT_MAGIC_ORDER[b.class_type] or 99&lt;br /&gt;
    if mmA ~= mmB then&lt;br /&gt;
        return mmA &amp;lt; mmB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return a.id &amp;lt; b.id&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the header cells&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addTh(tr, text)&lt;br /&gt;
    return tr:tag(&amp;#039;th&amp;#039;):wikitext(text)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the body cells&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addTd(tr, content)&lt;br /&gt;
    if type(content) == &amp;quot;string&amp;quot; then&lt;br /&gt;
        return tr:tag(&amp;#039;td&amp;#039;):wikitext(content)&lt;br /&gt;
    else&lt;br /&gt;
        return tr:tag(&amp;#039;td&amp;#039;):node(content)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator rows&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
        :addClass(&amp;#039;separator&amp;#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&amp;#039;td&amp;#039;):attr(&amp;#039;data-sort-value&amp;#039;, &amp;#039;&amp;#039;):attr(&amp;#039;colspan&amp;#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, isCompact)&lt;br /&gt;
    local tr = htmlTable:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
    addTh(tr, words.wiki_heroes_hero):attr(&amp;#039;colspan&amp;#039;, 2)&lt;br /&gt;
    addTh(tr, words.wiki_heroes_type)&lt;br /&gt;
    addTh(tr, words.wiki_heroes_class)&lt;br /&gt;
    addTh(tr, words.wiki_heroes_specialty):attr(&amp;#039;colspan&amp;#039;, 2)&lt;br /&gt;
    if not isCompact then&lt;br /&gt;
        addTh(tr, words.starting_skills)&lt;br /&gt;
        addTh(tr, words.starting_spells)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderFaction(faction, lang, frame)&lt;br /&gt;
    return frame:preprocess(&amp;#039;{{F|&amp;#039; .. faction .. &amp;#039;|&amp;#039; .. lang .. &amp;#039;}}&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIcon(nameEn, lang)&lt;br /&gt;
    local url = getUrl(nameEn, lang)&lt;br /&gt;
    return f(&amp;#039;[[File:%s.png|70px|link=%s]]&amp;#039;, nameEn, url)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderName(name, nameEn, lang)&lt;br /&gt;
    local url = getUrl(nameEn, lang)&lt;br /&gt;
    return f(&amp;#039;[[%s|%s]]&amp;#039;, url, name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderType(type, words, frame)&lt;br /&gt;
    local file = &amp;#039;[[File:&amp;#039; .. TYPE_ICONS[type] .. &amp;#039;.png|32px|link=]]&amp;#039;;&lt;br /&gt;
    local text = type == &amp;#039;magic&amp;#039; and words.wiki_magic or words.wiki_might&lt;br /&gt;
    return frame:preprocess(&amp;#039;{{Hint|&amp;#039; .. file .. &amp;#039;|&amp;#039; .. text .. &amp;#039;}}&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderClass(class, lang, frame)&lt;br /&gt;
    return frame:preprocess(&amp;#039;{{LinkIcon|type=hero_class|id=&amp;#039; .. class .. &amp;#039;|suffix=_icon.png|lang=&amp;#039; .. lang .. &amp;#039;}}&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderSpecialtyIcon(icon)&lt;br /&gt;
    icon = convertToTitleCase(icon)&lt;br /&gt;
    for key, value in pairs(SPEC_ICON_REPLACEMENTS) do&lt;br /&gt;
        icon = string.gsub(icon, key, value)&lt;br /&gt;
    end&lt;br /&gt;
    return f(&amp;#039;[[File:%s.png|64px|link=]]&amp;#039;, icon)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderSpecialtyText(name, summary, specId, words)&lt;br /&gt;
    local markup = [[&amp;lt;b&amp;gt;%s&amp;lt;/b&amp;gt;&amp;lt;br&amp;gt;%s &amp;lt;span class=&amp;#039;toggle mw-customtoggle-%s&amp;#039; role=&amp;#039;button&amp;#039; title=&amp;#039;%s&amp;#039;&amp;gt;…&amp;lt;/span&amp;gt;]]&lt;br /&gt;
    return f(markup, name, summary, shortenSpecId(specId), words.wiki_heroes_toggle)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderStartSkills(startSkills, skillsHub, lang)&lt;br /&gt;
    local items = {}&lt;br /&gt;
    local skillIds = mw.text.split(startSkills, &amp;#039;,&amp;#039;)&lt;br /&gt;
    if #skillIds &amp;gt; 1 then&lt;br /&gt;
        skillIds = { skillIds[2], skillIds[1] }&lt;br /&gt;
        for _, skillId in ipairs(skillIds) do&lt;br /&gt;
            local skill = skillsHub[skillId] or {}&lt;br /&gt;
            local ranks = skill.ranks or {}&lt;br /&gt;
            local rank = ranks[1] or { icon = &amp;#039;&amp;#039; }&lt;br /&gt;
            table.insert(items, {&lt;br /&gt;
                name = skill.name,&lt;br /&gt;
                nameEn = skill.nameEn,&lt;br /&gt;
                icon = rank.icon,&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    else&lt;br /&gt;
        local skill = skillsHub[skillIds[1]] or {}&lt;br /&gt;
        local ranks = skill.ranks or {}&lt;br /&gt;
        local rank = ranks[2] or { icon = &amp;#039;&amp;#039;, name = &amp;#039;&amp;#039; }&lt;br /&gt;
        table.insert(items, {&lt;br /&gt;
            name = rank.name,&lt;br /&gt;
            nameEn = skill.nameEn,&lt;br /&gt;
            icon = rank.icon,&lt;br /&gt;
        })&lt;br /&gt;
    end&lt;br /&gt;
    local lines = {}&lt;br /&gt;
    for _, item in ipairs(items) do&lt;br /&gt;
        local url = getUrl(item.nameEn, lang)&lt;br /&gt;
        local file = f(&amp;#039;[[File:%s.png|32px|link=%s]]&amp;#039;, item.icon, url);&lt;br /&gt;
        local link = &amp;#039;[[&amp;#039; .. url .. &amp;#039;|&amp;#039; .. item.name .. &amp;#039;]]&amp;#039;&lt;br /&gt;
        table.insert(lines, f(&amp;#039;&amp;lt;div&amp;gt;%s %s&amp;lt;/div&amp;gt;&amp;#039;, file, link))&lt;br /&gt;
    end&lt;br /&gt;
    return table.concat(lines, &amp;#039;&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderSpell(startMagics, frame, lang)&lt;br /&gt;
    if startMagics == &amp;#039;&amp;#039; then return &amp;#039;&amp;#039; end&lt;br /&gt;
&lt;br /&gt;
    local ids = mw.text.split(startMagics, &amp;#039;,&amp;#039;)&lt;br /&gt;
    local texts = {}&lt;br /&gt;
    for _, id in ipairs(ids) do&lt;br /&gt;
        if id ~= &amp;#039;change_use_necromancy&amp;#039; and not id:find(&amp;#039;astral_summon&amp;#039;) then&lt;br /&gt;
            local cleanId = string.gsub(id, &amp;#039;_special$&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
            local text = frame:preprocess(f(&amp;#039;{{LinkIcon|type=spell|id=%s|lang=%s}}&amp;#039;, cleanId, lang))&lt;br /&gt;
            --if cleanId ~= id then text = text .. &amp;#039;+&amp;#039; end&lt;br /&gt;
            table.insert(texts, text)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return table.concat(texts, &amp;#039;&amp;lt;br&amp;gt;&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function getSkillSortValue(startSkills)&lt;br /&gt;
    local skillIds = mw.text.split(startSkills, &amp;#039;,&amp;#039;)&lt;br /&gt;
    return skillIds[2] or skillIds[1]&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSpecDescription(htmlTable, description, specId)&lt;br /&gt;
    local markup = cleanHtmlWhitespace([[&lt;br /&gt;
        &amp;lt;tr class=&amp;#039;expand-child mw-collapsible mw-collapsed&amp;#039; id=&amp;#039;mw-customcollapsible-%s&amp;#039;&amp;gt;&lt;br /&gt;
            &amp;lt;td colspan=&amp;#039;100&amp;#039; class=&amp;#039;specialty-full-td&amp;#039;&amp;gt;&lt;br /&gt;
                %s&lt;br /&gt;
            &amp;lt;/td&amp;gt;&lt;br /&gt;
        &amp;lt;/tr&amp;gt;&lt;br /&gt;
    ]])&lt;br /&gt;
    htmlTable:node(f(markup, shortenSpecId(specId), description))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, heroes, lang, frame, words, skillsHub, isCompact)&lt;br /&gt;
    local currentType = #heroes &amp;gt; 0 and heroes[1].class_type&lt;br /&gt;
    local currentFaction = #heroes &amp;gt; 0 and heroes[1].faction&lt;br /&gt;
    for _, hero in ipairs(heroes) do&lt;br /&gt;
        -- separators&lt;br /&gt;
        if hero.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = hero.faction&lt;br /&gt;
            currentType = hero.class_type&lt;br /&gt;
            addSeparator(htmlTable, &amp;#039;separator-large&amp;#039;, renderFaction(currentFaction, lang, frame))&lt;br /&gt;
        elseif hero.class_type ~= currentType then&lt;br /&gt;
            currentType = hero.class_type&lt;br /&gt;
            addSeparator(htmlTable, &amp;#039;separator-tiny&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local skillSortValue = getSkillSortValue(hero.start_skills)&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&amp;#039;tr&amp;#039;):addClass(&amp;#039;hero&amp;#039;)&lt;br /&gt;
        addTd(tr, renderIcon(hero.nameEn, lang))&lt;br /&gt;
        addTd(tr, renderName(hero.name, hero.nameEn, lang))&lt;br /&gt;
        addTd(tr, renderType(hero.class_type, words, frame))&lt;br /&gt;
        addTd(tr, renderClass(hero.class_id, lang, frame))&lt;br /&gt;
        addTd(tr, renderSpecialtyIcon(hero.specIcon))&lt;br /&gt;
        addTd(tr, renderSpecialtyText(hero.specName, hero.specSummary, hero.specId, words))&lt;br /&gt;
        if not isCompact then&lt;br /&gt;
            addTd(tr, renderStartSkills(hero.start_skills, skillsHub, lang)):attr(&amp;#039;data-sort-value&amp;#039;, skillSortValue)&lt;br /&gt;
            addTd(tr, renderSpell(hero.start_magics, frame, lang))&lt;br /&gt;
        end&lt;br /&gt;
        addSpecDescription(htmlTable, hero.specDescription, hero.specId)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createToggleButton(heroes, words)&lt;br /&gt;
    local classes = {}&lt;br /&gt;
    for _, hero in ipairs(heroes) do&lt;br /&gt;
        table.insert(classes, &amp;#039;mw-customtoggle-&amp;#039; .. shortenSpecId(hero.specId))&lt;br /&gt;
    end&lt;br /&gt;
    local classesBlob = table.concat(classes, &amp;#039; &amp;#039;)&lt;br /&gt;
    local markup = cleanHtmlWhitespace([[&lt;br /&gt;
        &amp;lt;div class=&amp;#039;toggle-container&amp;#039;&amp;gt;&lt;br /&gt;
            &amp;lt;span role=&amp;#039;button&amp;#039; class=&amp;#039;toggle %s&amp;#039;&amp;gt;%s&amp;lt;/span&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    ]])&lt;br /&gt;
    return f(markup, classesBlob, words.wiki_heroes_toggle)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- M A I N&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
function p.display(frame)&lt;br /&gt;
    --do return dump(frame) end&lt;br /&gt;
&lt;br /&gt;
    -- Args&lt;br /&gt;
    local args = frame.args&lt;br /&gt;
    local lang = args.lang ~= &amp;#039;&amp;#039; and args.lang or &amp;#039;en&amp;#039;&lt;br /&gt;
    --lang = &amp;#039;ja&amp;#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &amp;#039;human&amp;#039;&lt;br /&gt;
    local forcedClass = args.class&lt;br /&gt;
    --forcedClass = &amp;#039;might_human&amp;#039;&lt;br /&gt;
    local forcedHeroes = args.heroes&lt;br /&gt;
    --forcedHeroes = &amp;#039;human_hero_1,human_hero_4&amp;#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
&lt;br /&gt;
    -- Text from the &amp;quot;Heroes Overview&amp;quot; overview&lt;br /&gt;
    local summaries = getSpecialtySummaries(lang)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local heroesResults = queryHero(lang, forcedFaction, forcedClass, forcedHeroes)&lt;br /&gt;
    local specializations = queryHeroSpecialization(lang)&lt;br /&gt;
    local skillsHub = SkillsOverview.getSkillsFromCargo(lang)&lt;br /&gt;
    local heroesHub = consolidateHeroes(lang, heroesResults, specializations, summaries)&lt;br /&gt;
    local heroes = flatten(heroesHub);&lt;br /&gt;
    if #heroes == 0 then return &amp;#039;No results&amp;#039; end&lt;br /&gt;
&lt;br /&gt;
    -- Various manipulations&lt;br /&gt;
    table.sort(heroes, sortHeroes)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&amp;#039;table&amp;#039;)&lt;br /&gt;
    htmlTable:addClass(&amp;#039;wikitable sortable&amp;#039;)&lt;br /&gt;
    local isCompact = forcedClass or forcedHeroes&lt;br /&gt;
    if isCompact then&lt;br /&gt;
        htmlTable:addClass(&amp;#039;is-compact&amp;#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, isCompact)&lt;br /&gt;
    createBody(htmlTable, heroes, lang, frame, words, skillsHub, isCompact)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local toggle = isCompact and &amp;#039;&amp;#039; or createToggleButton(heroes, words)&lt;br /&gt;
    local styleTag = frame:extensionTag(&amp;#039;templatestyles&amp;#039;, &amp;#039;&amp;#039;, { src = frame:getTitle() .. &amp;#039;/styles.css&amp;#039; })&lt;br /&gt;
    return &amp;#039;&amp;lt;div class=&amp;quot;heroes-overview&amp;quot;&amp;gt;&amp;#039; .. styleTag .. toggle .. tostring(htmlTable) .. &amp;#039;&amp;lt;/div&amp;gt;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561199054722510</name></author>
	</entry>
</feed>