<?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/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=76561198199147647</id>
	<title>Heroes of Might and Magic: Olden Era Official Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=76561198199147647"/>
	<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/Special:Contributions/76561198199147647"/>
	<updated>2026-10-26T16:31:33Z</updated>
	<subtitle>User contributions</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/Pillow/UnitsOverview&amp;diff=30578</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30578"/>
		<updated>2026-07-20T11:44:49Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 200&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
   	   local growth = row.units_weekly and string.format(&amp;quot;%+d&amp;quot;, units_weekly) or &#039;&#039;&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
            units[id].units_weekly = growths[id:gsub(&#039;_alt$&#039;, &#039;&#039;):gsub(&#039;_upg$&#039;, &#039;&#039;)]&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(u.units_weekly):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30576</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30576"/>
		<updated>2026-07-20T11:43:43Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 200&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
   	   local growth = row.units_weekly and string.format(&amp;quot;%+d&amp;quot;, units_weekly) or &#039;&#039;&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
            units[id].units_weekly = growths[id:gsub(&#039;_alt$&#039;, &#039;&#039;):gsub(&#039;_upg$&#039;, &#039;&#039;)]&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30574</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30574"/>
		<updated>2026-07-20T11:42:53Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 200&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
   	   local growth = row.units_weekly and string.format(&amp;quot;%+d&amp;quot;, numb) or &#039;&#039;&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
            units[id].units_weekly = growths[id:gsub(&#039;_alt$&#039;, &#039;&#039;):gsub(&#039;_upg$&#039;, &#039;&#039;)]&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview/styles.css&amp;diff=30554</id>
		<title>Module:UnitsOverview/styles.css</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview/styles.css&amp;diff=30554"/>
		<updated>2026-07-20T09:47:02Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Make Growth column centered&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ---- GENERAL ---- */&lt;br /&gt;
.units-overview table {&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .wiki-hint {&lt;br /&gt;
    border-bottom: none !important; /* hide the underline applied by the Hint template */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview th {&lt;br /&gt;
    padding-right: 4px !important; /* suppress the space allotted to the sortable markers */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview td {&lt;br /&gt;
    color: #cbced1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview tr:hover {&lt;br /&gt;
    background: #13161a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .forced-faction th:nth-child(2),&lt;br /&gt;
.units-overview .forced-faction td:nth-child(2) {&lt;br /&gt;
    display: none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .unit td:nth-child(1) {&lt;br /&gt;
    padding: 0 0.5em 0 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .unit.upgraded td:nth-child(1) {&lt;br /&gt;
    padding-left: 2em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- IMAGES ---- */&lt;br /&gt;
.units-overview th img {&lt;br /&gt;
    width: 24px;&lt;br /&gt;
    height: 24px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- STATS ---- */&lt;br /&gt;
.units-overview td:nth-child(6) img,&lt;br /&gt;
.units-overview td:nth-child(7) img,&lt;br /&gt;
.units-overview td:nth-child(8) img,&lt;br /&gt;
.units-overview td:nth-child(9) img,&lt;br /&gt;
.units-overview td:nth-child(10) img,&lt;br /&gt;
.units-overview td:nth-child(11) img,&lt;br /&gt;
.units-overview td:nth-child(12) img,&lt;br /&gt;
.units-overview td:nth-child(13) img {&lt;br /&gt;
    width: 16px;&lt;br /&gt;
    height: 16px;&lt;br /&gt;
    position: relative;&lt;br /&gt;
    top: -1px;&lt;br /&gt;
    margin-left: 1px;&lt;br /&gt;
    opacity: 0.5;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview td:nth-child(5) {&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
.units-overview td:nth-child(6),&lt;br /&gt;
.units-overview td:nth-child(7),&lt;br /&gt;
.units-overview td:nth-child(8),&lt;br /&gt;
.units-overview td:nth-child(9),&lt;br /&gt;
.units-overview td:nth-child(10),&lt;br /&gt;
.units-overview td:nth-child(11),&lt;br /&gt;
.units-overview td:nth-child(12),&lt;br /&gt;
.units-overview td:nth-child(13) {&lt;br /&gt;
    text-align: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ---- SEPARATORS ---- */&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator:first-child,&lt;br /&gt;
.units-overview .separator:last-child,&lt;br /&gt;
.units-overview .separator + .separator {&lt;br /&gt;
    display: none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-tiny {&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    background: #c8b36c !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-tiny td {&lt;br /&gt;
    padding: 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-large {&lt;br /&gt;
    background-color: #101622 !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font-size: 24px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-large img {&lt;br /&gt;
    width: 54px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- MORALE AND LUCK ---- */&lt;br /&gt;
.units-overview .elevated1 {&lt;br /&gt;
    color: #c8b36c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .elevated2 {&lt;br /&gt;
    color: #2ea821;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .elevated3 {&lt;br /&gt;
    color: #47adf2;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview/styles.css&amp;diff=30553</id>
		<title>Module:Sandbox/Pillow/UnitsOverview/styles.css</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview/styles.css&amp;diff=30553"/>
		<updated>2026-07-20T09:46:12Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ---- GENERAL ---- */&lt;br /&gt;
.units-overview table {&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .wiki-hint {&lt;br /&gt;
    border-bottom: none !important; /* hide the underline applied by the Hint template */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview th {&lt;br /&gt;
    padding-right: 4px !important; /* suppress the space allotted to the sortable markers */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview td {&lt;br /&gt;
    color: #cbced1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview tr:hover {&lt;br /&gt;
    background: #13161a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .forced-faction th:nth-child(2),&lt;br /&gt;
.units-overview .forced-faction td:nth-child(2) {&lt;br /&gt;
    display: none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .unit td:nth-child(1) {&lt;br /&gt;
    padding: 0 0.5em 0 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .unit.upgraded td:nth-child(1) {&lt;br /&gt;
    padding-left: 2em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- IMAGES ---- */&lt;br /&gt;
.units-overview th img {&lt;br /&gt;
    width: 24px;&lt;br /&gt;
    height: 24px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- STATS ---- */&lt;br /&gt;
.units-overview td:nth-child(6) img,&lt;br /&gt;
.units-overview td:nth-child(7) img,&lt;br /&gt;
.units-overview td:nth-child(8) img,&lt;br /&gt;
.units-overview td:nth-child(9) img,&lt;br /&gt;
.units-overview td:nth-child(10) img,&lt;br /&gt;
.units-overview td:nth-child(11) img,&lt;br /&gt;
.units-overview td:nth-child(12) img,&lt;br /&gt;
.units-overview td:nth-child(13) img {&lt;br /&gt;
    width: 16px;&lt;br /&gt;
    height: 16px;&lt;br /&gt;
    position: relative;&lt;br /&gt;
    top: -1px;&lt;br /&gt;
    margin-left: 1px;&lt;br /&gt;
    opacity: 0.5;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview td:nth-child(5) {&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
.units-overview td:nth-child(6),&lt;br /&gt;
.units-overview td:nth-child(7),&lt;br /&gt;
.units-overview td:nth-child(8),&lt;br /&gt;
.units-overview td:nth-child(9),&lt;br /&gt;
.units-overview td:nth-child(10),&lt;br /&gt;
.units-overview td:nth-child(11),&lt;br /&gt;
.units-overview td:nth-child(12),&lt;br /&gt;
.units-overview td:nth-child(13) {&lt;br /&gt;
    text-align: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ---- SEPARATORS ---- */&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator:first-child,&lt;br /&gt;
.units-overview .separator:last-child,&lt;br /&gt;
.units-overview .separator + .separator {&lt;br /&gt;
    display: none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-tiny {&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    background: #c8b36c !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-tiny td {&lt;br /&gt;
    padding: 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-large {&lt;br /&gt;
    background-color: #101622 !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font-size: 24px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-large img {&lt;br /&gt;
    width: 54px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- MORALE AND LUCK ---- */&lt;br /&gt;
.units-overview .elevated1 {&lt;br /&gt;
    color: #c8b36c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .elevated2 {&lt;br /&gt;
    color: #2ea821;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .elevated3 {&lt;br /&gt;
    color: #47adf2;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview/styles.css&amp;diff=30552</id>
		<title>Module:Sandbox/Pillow/UnitsOverview/styles.css</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview/styles.css&amp;diff=30552"/>
		<updated>2026-07-20T09:45:16Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ---- GENERAL ---- */&lt;br /&gt;
.units-overview table {&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .wiki-hint {&lt;br /&gt;
    border-bottom: none !important; /* hide the underline applied by the Hint template */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview th {&lt;br /&gt;
    padding-right: 4px !important; /* suppress the space allotted to the sortable markers */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview td {&lt;br /&gt;
    color: #cbced1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview tr:hover {&lt;br /&gt;
    background: #13161a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .forced-faction th:nth-child(2),&lt;br /&gt;
.units-overview .forced-faction td:nth-child(2) {&lt;br /&gt;
    display: none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .unit td:nth-child(1) {&lt;br /&gt;
    padding: 0 0.5em 0 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .unit.upgraded td:nth-child(1) {&lt;br /&gt;
    padding-left: 2em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- IMAGES ---- */&lt;br /&gt;
.units-overview th img {&lt;br /&gt;
    width: 24px;&lt;br /&gt;
    height: 24px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- STATS ---- */&lt;br /&gt;
.units-overview td:nth-child(6) img,&lt;br /&gt;
.units-overview td:nth-child(7) img,&lt;br /&gt;
.units-overview td:nth-child(8) img,&lt;br /&gt;
.units-overview td:nth-child(9) img,&lt;br /&gt;
.units-overview td:nth-child(10) img,&lt;br /&gt;
.units-overview td:nth-child(11) img,&lt;br /&gt;
.units-overview td:nth-child(12) img,&lt;br /&gt;
.units-overview td:nth-child(13) img {&lt;br /&gt;
    width: 16px;&lt;br /&gt;
    height: 16px;&lt;br /&gt;
    position: relative;&lt;br /&gt;
    top: -1px;&lt;br /&gt;
    margin-left: 1px;&lt;br /&gt;
    opacity: 0.5;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview td:nth-child(5),&lt;br /&gt;
.units-overview td:nth-child(6),&lt;br /&gt;
.units-overview td:nth-child(7),&lt;br /&gt;
.units-overview td:nth-child(8),&lt;br /&gt;
.units-overview td:nth-child(9),&lt;br /&gt;
.units-overview td:nth-child(10),&lt;br /&gt;
.units-overview td:nth-child(11),&lt;br /&gt;
.units-overview td:nth-child(12),&lt;br /&gt;
.units-overview td:nth-child(13) {&lt;br /&gt;
    text-align: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ---- SEPARATORS ---- */&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator:first-child,&lt;br /&gt;
.units-overview .separator:last-child,&lt;br /&gt;
.units-overview .separator + .separator {&lt;br /&gt;
    display: none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-tiny {&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    background: #c8b36c !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-tiny td {&lt;br /&gt;
    padding: 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-large {&lt;br /&gt;
    background-color: #101622 !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font-size: 24px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-large img {&lt;br /&gt;
    width: 54px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- MORALE AND LUCK ---- */&lt;br /&gt;
.units-overview .elevated1 {&lt;br /&gt;
    color: #c8b36c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .elevated2 {&lt;br /&gt;
    color: #2ea821;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .elevated3 {&lt;br /&gt;
    color: #47adf2;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview/styles.css&amp;diff=30551</id>
		<title>Module:Sandbox/Pillow/UnitsOverview/styles.css</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview/styles.css&amp;diff=30551"/>
		<updated>2026-07-20T09:44:00Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Created page with &amp;quot;/* ---- GENERAL ---- */ .units-overview table {     white-space: nowrap; }  .units-overview .wiki-hint {     border-bottom: none !important; /* hide the underline applied by the Hint template */ }  .units-overview th {     padding-right: 4px !important; /* suppress the space allotted to the sortable markers */ }  .units-overview td {     color: #cbced1; }  .units-overview tr:hover {     background: #13161a; }  .units-overview .forced-faction th:nth-child(2), .units-overv...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ---- GENERAL ---- */&lt;br /&gt;
.units-overview table {&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .wiki-hint {&lt;br /&gt;
    border-bottom: none !important; /* hide the underline applied by the Hint template */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview th {&lt;br /&gt;
    padding-right: 4px !important; /* suppress the space allotted to the sortable markers */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview td {&lt;br /&gt;
    color: #cbced1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview tr:hover {&lt;br /&gt;
    background: #13161a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .forced-faction th:nth-child(2),&lt;br /&gt;
.units-overview .forced-faction td:nth-child(2) {&lt;br /&gt;
    display: none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .unit td:nth-child(1) {&lt;br /&gt;
    padding: 0 0.5em 0 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .unit.upgraded td:nth-child(1) {&lt;br /&gt;
    padding-left: 2em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- IMAGES ---- */&lt;br /&gt;
.units-overview th img {&lt;br /&gt;
    width: 24px;&lt;br /&gt;
    height: 24px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- STATS ---- */&lt;br /&gt;
.units-overview td:nth-child(6) img,&lt;br /&gt;
.units-overview td:nth-child(7) img,&lt;br /&gt;
.units-overview td:nth-child(8) img,&lt;br /&gt;
.units-overview td:nth-child(9) img,&lt;br /&gt;
.units-overview td:nth-child(10) img,&lt;br /&gt;
.units-overview td:nth-child(11) img,&lt;br /&gt;
.units-overview td:nth-child(12) img,&lt;br /&gt;
.units-overview td:nth-child(13) img {&lt;br /&gt;
    width: 16px;&lt;br /&gt;
    height: 16px;&lt;br /&gt;
    position: relative;&lt;br /&gt;
    top: -1px;&lt;br /&gt;
    margin-left: 1px;&lt;br /&gt;
    opacity: 0.5;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview td:nth-child(6),&lt;br /&gt;
.units-overview td:nth-child(7),&lt;br /&gt;
.units-overview td:nth-child(8),&lt;br /&gt;
.units-overview td:nth-child(9),&lt;br /&gt;
.units-overview td:nth-child(10),&lt;br /&gt;
.units-overview td:nth-child(11),&lt;br /&gt;
.units-overview td:nth-child(12),&lt;br /&gt;
.units-overview td:nth-child(13) {&lt;br /&gt;
    text-align: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ---- SEPARATORS ---- */&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator:first-child,&lt;br /&gt;
.units-overview .separator:last-child,&lt;br /&gt;
.units-overview .separator + .separator {&lt;br /&gt;
    display: none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-tiny {&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    background: #c8b36c !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-tiny td {&lt;br /&gt;
    padding: 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-large {&lt;br /&gt;
    background-color: #101622 !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font-size: 24px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-large img {&lt;br /&gt;
    width: 54px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- MORALE AND LUCK ---- */&lt;br /&gt;
.units-overview .elevated1 {&lt;br /&gt;
    color: #c8b36c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .elevated2 {&lt;br /&gt;
    color: #2ea821;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .elevated3 {&lt;br /&gt;
    color: #47adf2;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview/styles.css&amp;diff=30544</id>
		<title>Module:UnitsOverview/styles.css</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview/styles.css&amp;diff=30544"/>
		<updated>2026-07-20T09:39:50Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Changes to account for a newly added column (Growth)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ---- GENERAL ---- */&lt;br /&gt;
.units-overview table {&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .wiki-hint {&lt;br /&gt;
    border-bottom: none !important; /* hide the underline applied by the Hint template */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview th {&lt;br /&gt;
    padding-right: 4px !important; /* suppress the space allotted to the sortable markers */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview td {&lt;br /&gt;
    color: #cbced1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview tr:hover {&lt;br /&gt;
    background: #13161a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .forced-faction th:nth-child(2),&lt;br /&gt;
.units-overview .forced-faction td:nth-child(2) {&lt;br /&gt;
    display: none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .unit td:nth-child(1) {&lt;br /&gt;
    padding: 0 0.5em 0 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .unit.upgraded td:nth-child(1) {&lt;br /&gt;
    padding-left: 2em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- IMAGES ---- */&lt;br /&gt;
.units-overview th img {&lt;br /&gt;
    width: 24px;&lt;br /&gt;
    height: 24px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- STATS ---- */&lt;br /&gt;
.units-overview td:nth-child(6) img,&lt;br /&gt;
.units-overview td:nth-child(7) img,&lt;br /&gt;
.units-overview td:nth-child(8) img,&lt;br /&gt;
.units-overview td:nth-child(9) img,&lt;br /&gt;
.units-overview td:nth-child(10) img,&lt;br /&gt;
.units-overview td:nth-child(11) img,&lt;br /&gt;
.units-overview td:nth-child(12) img,&lt;br /&gt;
.units-overview td:nth-child(13) img {&lt;br /&gt;
    width: 16px;&lt;br /&gt;
    height: 16px;&lt;br /&gt;
    position: relative;&lt;br /&gt;
    top: -1px;&lt;br /&gt;
    margin-left: 1px;&lt;br /&gt;
    opacity: 0.5;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview td:nth-child(6),&lt;br /&gt;
.units-overview td:nth-child(7),&lt;br /&gt;
.units-overview td:nth-child(8),&lt;br /&gt;
.units-overview td:nth-child(9),&lt;br /&gt;
.units-overview td:nth-child(10),&lt;br /&gt;
.units-overview td:nth-child(11),&lt;br /&gt;
.units-overview td:nth-child(12),&lt;br /&gt;
.units-overview td:nth-child(13) {&lt;br /&gt;
    text-align: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ---- SEPARATORS ---- */&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator:first-child,&lt;br /&gt;
.units-overview .separator:last-child,&lt;br /&gt;
.units-overview .separator + .separator {&lt;br /&gt;
    display: none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-tiny {&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    background: #c8b36c !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-tiny td {&lt;br /&gt;
    padding: 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-large {&lt;br /&gt;
    background-color: #101622 !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font-size: 24px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .separator-large img {&lt;br /&gt;
    width: 54px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ---- MORALE AND LUCK ---- */&lt;br /&gt;
.units-overview .elevated1 {&lt;br /&gt;
    color: #c8b36c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .elevated2 {&lt;br /&gt;
    color: #2ea821;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.units-overview .elevated3 {&lt;br /&gt;
    color: #47adf2;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Data:WikiTranslations/ru&amp;diff=30543</id>
		<title>Data:WikiTranslations/ru</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Data:WikiTranslations/ru&amp;diff=30543"/>
		<updated>2026-07-20T09:36:08Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Add wiki_units_weekly&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Loc}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Common ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_internal_id&lt;br /&gt;
| en = Internal ID&lt;br /&gt;
| name = Внутренний ID&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_might&lt;br /&gt;
| en = Might&lt;br /&gt;
| name = Might💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_magic&lt;br /&gt;
| en = Magic&lt;br /&gt;
| name = Magic💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_cost&lt;br /&gt;
| en = Cost&lt;br /&gt;
| name = Цена&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_name&lt;br /&gt;
| en = Name&lt;br /&gt;
| name = Имя&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_icon&lt;br /&gt;
| en = Icon&lt;br /&gt;
| name = Иконка&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_skills&lt;br /&gt;
| en = Skills&lt;br /&gt;
| name = Навыки&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_sub_skills&lt;br /&gt;
| en = Subskills&lt;br /&gt;
| name = Поднавыки&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_none&lt;br /&gt;
| en = None&lt;br /&gt;
| name = None💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_level&lt;br /&gt;
| en = Level&lt;br /&gt;
| name = Level💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_total&lt;br /&gt;
| en = Total&lt;br /&gt;
| name = Total💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Hero Infobox ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_hero_class&lt;br /&gt;
| en = Hero Class&lt;br /&gt;
| name = Класс&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_hero_spec&lt;br /&gt;
| en = Specialization&lt;br /&gt;
| name = Специализация&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_portrait&lt;br /&gt;
| en = Portrait&lt;br /&gt;
| name = Портрет&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_start_skill&lt;br /&gt;
| en = Starting Skills&lt;br /&gt;
| name = Стартовые навыки&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_start_spell&lt;br /&gt;
| en = Starting Spells&lt;br /&gt;
| name = Заклинание&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_all_heroes&lt;br /&gt;
| en = All Heroes&lt;br /&gt;
| name = Все герои&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Heroes Navbox ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_navbox&lt;br /&gt;
| target_id = wiki_heroes_by_class&lt;br /&gt;
| en = Heroes by Class&lt;br /&gt;
| name = Герои&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Template:HeroClassNavbox ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_class_navbox&lt;br /&gt;
| target_id = wiki_hero_classes&lt;br /&gt;
| en = Hero Classes&lt;br /&gt;
| name = Классы героев&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Template:SpellsNavbox ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_navbox&lt;br /&gt;
| target_id = wiki_spells_navbox_overview&lt;br /&gt;
| en = Spells Overview&lt;br /&gt;
| name = Список заклинаний&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Template:SpellInfobox/base ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_tier&lt;br /&gt;
| en = Tier&lt;br /&gt;
| name = Ранг&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_mana&lt;br /&gt;
| en = Mana&lt;br /&gt;
| name = Мана&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_school&lt;br /&gt;
| en = School&lt;br /&gt;
| name = Школа&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_neutral&lt;br /&gt;
| en = Neutral Magic&lt;br /&gt;
| name = Нейтральная магия&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_type&lt;br /&gt;
| en = Type&lt;br /&gt;
| name = Тип&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_battle&lt;br /&gt;
| en = Battle Spell&lt;br /&gt;
| name = Боевое заклинание&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_world&lt;br /&gt;
| en = Global Map Spell&lt;br /&gt;
| name = Global Map Spell💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_effect&lt;br /&gt;
| en = Effect&lt;br /&gt;
| name = Эффект&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_effects&lt;br /&gt;
| en = Effects at higher levels&lt;br /&gt;
| name = Effects at higher levels💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_level&lt;br /&gt;
| en = Level&lt;br /&gt;
| name = Уровень&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_masterful&lt;br /&gt;
| en = Masterful Version&lt;br /&gt;
| name = Искусная версия&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_heroes&lt;br /&gt;
| en = Heroes that start with this spell&lt;br /&gt;
| name = Heroes that start with this spell💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_construct_immunities&lt;br /&gt;
| en = Does not affect Constructs&lt;br /&gt;
| name = Не действует на конструкты&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_demon_undead_embodiment_construct_immunities&lt;br /&gt;
| en = Does not affect Hive Spawns, Undead, Embodiments, or Constructs&lt;br /&gt;
| name = Не действует на порождения Роя, нежить, воплощения и конструкты&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_earth_immunities&lt;br /&gt;
| en = Does not affect [[Vine_Iriyad{{!}}Iriyads]]&lt;br /&gt;
| name = Не действует на ириадов&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_embodiment_construct_immunities&lt;br /&gt;
| en = Does not affect Embodiments or Constructs&lt;br /&gt;
| name = Не действует на воплощения и конструкты&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_embodiment_immunities&lt;br /&gt;
| en = Does not affect Embodiments&lt;br /&gt;
| name = Не действует на воплощения&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_fire_immunities&lt;br /&gt;
| en = Does not affect [[Phoenix{{!}}Phoenixes]]&lt;br /&gt;
| name = Не действует на фениксов&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_ice_immunities&lt;br /&gt;
| en = Does not affect [[Naiad{{!}}Naiads]]&lt;br /&gt;
| name = Не действует на наяд&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_light_immunities&lt;br /&gt;
| en = Does not affect [[Qilin{{!}}Qilins]]&lt;br /&gt;
| name = Не действует на киринов&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_magic_creature_undead_construct_immunities&lt;br /&gt;
| en = Does not affect Magic Creatures, Undead, or Constructs&lt;br /&gt;
| name = Не действует на магических существ, нежить и конструкты&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_undead_construct_immunities&lt;br /&gt;
| en = Does not affect Undead or Constructs&lt;br /&gt;
| name = Не действует на нежить и конструкты&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_undead_embodiment_construct_immunities&lt;br /&gt;
| en = Does not affect Undead, Embodiments, or Constructs&lt;br /&gt;
| name = Не действует на нежить, воплощения и конструкты&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_undead_embodiment_construct_immunities_no_heal&lt;br /&gt;
| en = Healing does not affect Undead, Embodiments, or Constructs&lt;br /&gt;
| name = Исцеление не действует на нежить, воплощения и конструкты&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_undead_immunities&lt;br /&gt;
| en = Does not affect the Undead&lt;br /&gt;
| name = Не действует на нежить&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_need_3_level&lt;br /&gt;
| en = The hero must be at least level 3 to learn this spell&lt;br /&gt;
| name = Чтобы изучить заклинание, герой должен быть как минимум 3‑го уровня.&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_need_4_level&lt;br /&gt;
| en = The hero must be at least level 4 to learn this spell&lt;br /&gt;
| name = Чтобы изучить заклинание, герой должен быть как минимум 4‑го уровня.&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_need_5_level&lt;br /&gt;
| en = The hero must be at least level 5 to learn this spell&lt;br /&gt;
| name = Чтобы изучить заклинание, герой должен быть как минимум 5‑го уровня.&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Units Overview ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_weekly&lt;br /&gt;
| en = Growth&lt;br /&gt;
| name = Прирост&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_neutral&lt;br /&gt;
| en = Neutral&lt;br /&gt;
| name = Нейтральные&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_attack_type&lt;br /&gt;
| en = Attack type&lt;br /&gt;
| name = Тип атаки&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_no_counter&lt;br /&gt;
| en = Melee,&amp;lt;br&amp;gt;no counter&lt;br /&gt;
| name = Ближний бой,&amp;lt;br&amp;gt;без контратаки&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_no_close&lt;br /&gt;
| en = Ranged,&amp;lt;br&amp;gt;no melee penalty&lt;br /&gt;
| name = Выстрел,&amp;lt;br&amp;gt;без штрафа в рукопашной&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_no_range&lt;br /&gt;
| en = Ranged,&amp;lt;br&amp;gt;no range penalty&lt;br /&gt;
| name = Выстрел,&amp;lt;br&amp;gt;без штрафа расстояния&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_no_range_close&lt;br /&gt;
| en = Ranged,&amp;lt;br&amp;gt;no penalties&lt;br /&gt;
| name = Выстрел,&amp;lt;br&amp;gt;без штрафа&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Laws ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = laws_overview&lt;br /&gt;
| target_id = wiki_laws_side&lt;br /&gt;
| en = Side&lt;br /&gt;
| name = Side💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = laws_overview&lt;br /&gt;
| target_id = wiki_laws_law&lt;br /&gt;
| en = Law&lt;br /&gt;
| name = Закон&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Spells ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_overview&lt;br /&gt;
| target_id = wiki_spells_school&lt;br /&gt;
| en = School&lt;br /&gt;
| name = Школа&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_overview&lt;br /&gt;
| target_id = wiki_spells_masterful&lt;br /&gt;
| en = Masterful&lt;br /&gt;
| name = Искусное&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_overview&lt;br /&gt;
| target_id = wiki_spells_has_masterful&lt;br /&gt;
| en = Has Masterful&amp;lt;br&amp;gt;version&lt;br /&gt;
| name = Имеет искусную&amp;lt;br&amp;gt;версию&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_overview&lt;br /&gt;
| target_id = wiki_spells_no_masterful&lt;br /&gt;
| en = No Masterful&amp;lt;br&amp;gt;version available&lt;br /&gt;
| name = Нет имеет&amp;lt;br&amp;gt;искусной версии&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_overview&lt;br /&gt;
| target_id = wiki_spells_level&lt;br /&gt;
| en = Level&lt;br /&gt;
| name = Уровень&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Skills ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skill_page&lt;br /&gt;
| target_id = wiki_skills_levels&lt;br /&gt;
| en = Levels&lt;br /&gt;
| name = Уровни&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skill_page&lt;br /&gt;
| target_id = wiki_skills_synergies&lt;br /&gt;
| en = Skill Synergies&lt;br /&gt;
| name = Синергия навыков&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skill_page&lt;br /&gt;
| target_id = wiki_skills_artifacts&lt;br /&gt;
| en = Artifact Effects&lt;br /&gt;
| name = Эффект артефакта&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skills_overview&lt;br /&gt;
| target_id = wiki_skills_might&lt;br /&gt;
| en = Might Skills&lt;br /&gt;
| name = Навыки воина&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skills_overview&lt;br /&gt;
| target_id = wiki_skills_magic&lt;br /&gt;
| en = Magic Skills&lt;br /&gt;
| name = Навыки мага&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skills_overview&lt;br /&gt;
| target_id = wiki_skills_general&lt;br /&gt;
| en = General Skills&lt;br /&gt;
| name = Общие навыки&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skills_overview&lt;br /&gt;
| target_id = wiki_skills_faction&lt;br /&gt;
| en = Faction Skills&lt;br /&gt;
| name = Фракционные навыки&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Heroes Overview ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_overview&lt;br /&gt;
| target_id = wiki_heroes_hero&lt;br /&gt;
| en = Hero&lt;br /&gt;
| name = Герой&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_overview&lt;br /&gt;
| target_id = wiki_heroes_type&lt;br /&gt;
| en = Type&lt;br /&gt;
| name = Тип&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_overview&lt;br /&gt;
| target_id = wiki_heroes_class&lt;br /&gt;
| en = Class&lt;br /&gt;
| name = Класс&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_overview&lt;br /&gt;
| target_id = wiki_heroes_specialty&lt;br /&gt;
| en = Specialty&lt;br /&gt;
| name = Специализация&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_overview&lt;br /&gt;
| target_id = wiki_heroes_toggle&lt;br /&gt;
| en = Toggle specialty&lt;br /&gt;
| name = Раскрыть&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Module:ArtifactsOverview ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_artifact&lt;br /&gt;
| en = Artifact&lt;br /&gt;
| name = Artifact💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_upgrade&lt;br /&gt;
| en = Upgrade&lt;br /&gt;
| name = Upgrade💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_upgrade_cost&lt;br /&gt;
| en = Upgrade cost&lt;br /&gt;
| name = Upgrade cost💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_slot&lt;br /&gt;
| en = Slot&lt;br /&gt;
| name = Slot💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_rarity&lt;br /&gt;
| en = Rarity&lt;br /&gt;
| name = Rarity💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_set&lt;br /&gt;
| en = Set&lt;br /&gt;
| name = Set💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_armor&lt;br /&gt;
| en = Armor&lt;br /&gt;
| name = Armor💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_back&lt;br /&gt;
| en = Back&lt;br /&gt;
| name = Back💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_belt&lt;br /&gt;
| en = Belt&lt;br /&gt;
| name = Belt💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_boots&lt;br /&gt;
| en = Boots&lt;br /&gt;
| name = Boots💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_head&lt;br /&gt;
| en = Head&lt;br /&gt;
| name = Head💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_item_slot&lt;br /&gt;
| en = Item&lt;br /&gt;
| name = Item💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_left_hand&lt;br /&gt;
| en = Main Hand&lt;br /&gt;
| name = Main Hand💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_right_hand&lt;br /&gt;
| en = Off Hand&lt;br /&gt;
| name = Off Hand💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_ring&lt;br /&gt;
| en = Rings&lt;br /&gt;
| name = Rings💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_unic_slot&lt;br /&gt;
| en = Relic&lt;br /&gt;
| name = Relic💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_common&lt;br /&gt;
| en = Common&lt;br /&gt;
| name = Common💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_rare&lt;br /&gt;
| en = Rare&lt;br /&gt;
| name = Rare💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_epic&lt;br /&gt;
| en = Epic&lt;br /&gt;
| name = Epic💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_legendary&lt;br /&gt;
| en = Legendary&lt;br /&gt;
| name = Legendary💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_special&lt;br /&gt;
| en = Special&lt;br /&gt;
| name = Special💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_items&lt;br /&gt;
| en = items:&lt;br /&gt;
| name = items:💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_toggle&lt;br /&gt;
| en = Toggle lore text&lt;br /&gt;
| name = Toggle lore text💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_no_upgrade&lt;br /&gt;
| en = Cannot be upgraded.&lt;br /&gt;
| name = Нельзя улучшить.&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_no_set&lt;br /&gt;
| en = Not part of a set.&lt;br /&gt;
| name = Not part of a set.💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_back_to_top&lt;br /&gt;
| en = back to top&lt;br /&gt;
| name = back to top💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Template:UnitInfobox/base ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_upgrade_cost&lt;br /&gt;
| en = Upgrade cost&lt;br /&gt;
| name = Улучшение&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_growth&lt;br /&gt;
| en = Base growth&lt;br /&gt;
| name = Прирост&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_squad&lt;br /&gt;
| en = Squad value&lt;br /&gt;
| name = Squad value💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_stats&lt;br /&gt;
| en = Stats&lt;br /&gt;
| name = Характеристики&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_other&lt;br /&gt;
| en = Other Info&lt;br /&gt;
| name = Прочее&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_dwelling&lt;br /&gt;
| en = Dwelling&lt;br /&gt;
| name = Жилище&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_bank&lt;br /&gt;
| en = Creature bank&lt;br /&gt;
| name = Банк существ&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_specialist&lt;br /&gt;
| en = Specialist hero&lt;br /&gt;
| name = Герой-специалист&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_related&lt;br /&gt;
| en = Related Units&lt;br /&gt;
| name = Связанные существа&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_base&lt;br /&gt;
| en = Base unit&lt;br /&gt;
| name = Базовое&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_upgrade_a&lt;br /&gt;
| en = Upgrade A&lt;br /&gt;
| name = Улучшение A&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_upgrade_b&lt;br /&gt;
| en = Upgrade B&lt;br /&gt;
| name = Улучшение B&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== [[Template:StatDiff]] ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = stat_diff&lt;br /&gt;
| target_id = wiki_diff_higher&lt;br /&gt;
| en = This stat is higher compared to the base unit.&lt;br /&gt;
| name = This stat is higher compared to the base unit.💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = stat_diff&lt;br /&gt;
| target_id = wiki_diff_lower&lt;br /&gt;
| en = This stat is lower compared to the base unit.&lt;br /&gt;
| name = This stat is lower compared to the base unit.💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = stat_diff&lt;br /&gt;
| target_id = wiki_diff_base&lt;br /&gt;
| en = Base:&lt;br /&gt;
| name = Base:💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = stat_diff&lt;br /&gt;
| target_id = wiki_diff_increase&lt;br /&gt;
| en = Increase:&lt;br /&gt;
| name = Increase:💬&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = stat_diff&lt;br /&gt;
| target_id = wiki_diff_decrease&lt;br /&gt;
| en = Decrease:&lt;br /&gt;
| name = Decrease:💬&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Category: Wiki Translations]]&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Data:WikiTranslations/en&amp;diff=30541</id>
		<title>Data:WikiTranslations/en</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Data:WikiTranslations/en&amp;diff=30541"/>
		<updated>2026-07-20T09:33:30Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Add wiki_units_weekly&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Loc}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Common ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_internal_id&lt;br /&gt;
| en = Internal ID&lt;br /&gt;
| name = Internal ID&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_might&lt;br /&gt;
| en = Might&lt;br /&gt;
| name = Might&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_magic&lt;br /&gt;
| en = Magic&lt;br /&gt;
| name = Magic&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_cost&lt;br /&gt;
| en = Cost&lt;br /&gt;
| name = Cost&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_name&lt;br /&gt;
| en = Name&lt;br /&gt;
| name = Name&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_icon&lt;br /&gt;
| en = Icon&lt;br /&gt;
| name = Icon&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_skills&lt;br /&gt;
| en = Skills&lt;br /&gt;
| name = Skills&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_sub_skills&lt;br /&gt;
| en = Subskills&lt;br /&gt;
| name = Subskills&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_none&lt;br /&gt;
| en = None&lt;br /&gt;
| name = None&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_level&lt;br /&gt;
| en = Level&lt;br /&gt;
| name = Level&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = common&lt;br /&gt;
| target_id = wiki_total&lt;br /&gt;
| en = Total&lt;br /&gt;
| name = Total&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Hero Infobox ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_hero_class&lt;br /&gt;
| en = Hero Class&lt;br /&gt;
| name = Hero Class&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_hero_spec&lt;br /&gt;
| en = Specialization&lt;br /&gt;
| name = Specialization&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_portrait&lt;br /&gt;
| en = Portrait&lt;br /&gt;
| name = Portrait&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_start_skill&lt;br /&gt;
| en = Starting Skills&lt;br /&gt;
| name = Starting Skills&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_start_spell&lt;br /&gt;
| en = Starting Spells&lt;br /&gt;
| name = Starting Spells&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_infobox&lt;br /&gt;
| target_id = wiki_all_heroes&lt;br /&gt;
| en = All Heroes&lt;br /&gt;
| name = All Heroes&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Heroes Navbox ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_navbox&lt;br /&gt;
| target_id = wiki_heroes_by_class&lt;br /&gt;
| en = Heroes by Class&lt;br /&gt;
| name = Heroes by Class&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Template:HeroClassNavbox ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = hero_class_navbox&lt;br /&gt;
| target_id = wiki_hero_classes&lt;br /&gt;
| en = Hero Classes&lt;br /&gt;
| name = Hero Classes&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Template:SpellsNavbox ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_navbox&lt;br /&gt;
| target_id = wiki_spells_navbox_overview&lt;br /&gt;
| en = Spells Overview&lt;br /&gt;
| name = Spells Overview&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Template:SpellInfobox/base ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_tier&lt;br /&gt;
| en = Tier&lt;br /&gt;
| name = Tier&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_mana&lt;br /&gt;
| en = Mana&lt;br /&gt;
| name = Mana&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_school&lt;br /&gt;
| en = School&lt;br /&gt;
| name = School&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_neutral&lt;br /&gt;
| en = Neutral Magic&lt;br /&gt;
| name = Neutral Magic&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_type&lt;br /&gt;
| en = Type&lt;br /&gt;
| name = Type&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_battle&lt;br /&gt;
| en = Battle Spell&lt;br /&gt;
| name = Battle Spell&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_world&lt;br /&gt;
| en = Global Map Spell&lt;br /&gt;
| name = Global Map Spell&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_effect&lt;br /&gt;
| en = Effect&lt;br /&gt;
| name = Effect&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_effects&lt;br /&gt;
| en = Effects at higher levels&lt;br /&gt;
| name = Effects at higher levels&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_level&lt;br /&gt;
| en = Level&lt;br /&gt;
| name = Level&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_masterful&lt;br /&gt;
| en = Masterful Version&lt;br /&gt;
| name = Masterful Version&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_spellbox_heroes&lt;br /&gt;
| en = Heroes that start with this spell&lt;br /&gt;
| name = Heroes that start with this spell&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_construct_immunities&lt;br /&gt;
| en = Does not affect Constructs&lt;br /&gt;
| name = Does not affect Constructs&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_demon_undead_embodiment_construct_immunities&lt;br /&gt;
| en = Does not affect Hive Spawns, Undead, Embodiments, or Constructs&lt;br /&gt;
| name = Does not affect Hive Spawns, Undead, Embodiments, or Constructs&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_earth_immunities&lt;br /&gt;
| en = Does not affect [[Vine_Iriyad{{!}}Iriyads]]&lt;br /&gt;
| name = Does not affect [[Vine_Iriyad{{!}}Iriyads]]&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_embodiment_construct_immunities&lt;br /&gt;
| en = Does not affect Embodiments or Constructs&lt;br /&gt;
| name = Does not affect Embodiments or Constructs&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_embodiment_immunities&lt;br /&gt;
| en = Does not affect Embodiments&lt;br /&gt;
| name = Does not affect Embodiments&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_fire_immunities&lt;br /&gt;
| en = Does not affect [[Phoenix{{!}}Phoenixes]]&lt;br /&gt;
| name = Does not affect [[Phoenix{{!}}Phoenixes]]&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_ice_immunities&lt;br /&gt;
| en = Does not affect [[Naiad{{!}}Naiads]]&lt;br /&gt;
| name = Does not affect [[Naiad{{!}}Naiads]]&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_light_immunities&lt;br /&gt;
| en = Does not affect [[Qilin{{!}}Qilins]]&lt;br /&gt;
| name = Does not affect [[Qilin{{!}}Qilins]]&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_magic_creature_undead_construct_immunities&lt;br /&gt;
| en = Does not affect Magic Creatures, Undead, or Constructs&lt;br /&gt;
| name = Does not affect Magic Creatures, Undead, or Constructs&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_undead_construct_immunities&lt;br /&gt;
| en = Does not affect Undead or Constructs&lt;br /&gt;
| name = Does not affect Undead or Constructs&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_undead_embodiment_construct_immunities&lt;br /&gt;
| en = Does not affect Undead, Embodiments, or Constructs&lt;br /&gt;
| name = Does not affect Undead, Embodiments, or Constructs&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_undead_embodiment_construct_immunities_no_heal&lt;br /&gt;
| en = Healing does not affect Undead, Embodiments, or Constructs&lt;br /&gt;
| name = Healing does not affect Undead, Embodiments, or Constructs&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_effect_undead_immunities&lt;br /&gt;
| en = Does not affect the Undead&lt;br /&gt;
| name = Does not affect the Undead&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_need_3_level&lt;br /&gt;
| en = The hero must be at least level 3 to learn this spell&lt;br /&gt;
| name = The hero must be at least level 3 to learn this spell&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_need_4_level&lt;br /&gt;
| en = The hero must be at least level 4 to learn this spell&lt;br /&gt;
| name = The hero must be at least level 4 to learn this spell&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spellbox&lt;br /&gt;
| target_id = wiki_description_need_5_level&lt;br /&gt;
| en = The hero must be at least level 5 to learn this spell&lt;br /&gt;
| name = The hero must be at least level 5 to learn this spell&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Units Overview ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_weekly&lt;br /&gt;
| en = Growth&lt;br /&gt;
| name = Growth&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_neutral&lt;br /&gt;
| en = Neutral&lt;br /&gt;
| name = Neutral&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_attack_type&lt;br /&gt;
| en = Attack type&lt;br /&gt;
| name = Attack type&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_no_counter&lt;br /&gt;
| en = Melee,&amp;lt;br&amp;gt;no counter&lt;br /&gt;
| name = Melee,&amp;lt;br&amp;gt;no counter&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_no_close&lt;br /&gt;
| en = Ranged,&amp;lt;br&amp;gt;no melee penalty&lt;br /&gt;
| name = Ranged,&amp;lt;br&amp;gt;no melee penalty&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_no_range&lt;br /&gt;
| en = Ranged,&amp;lt;br&amp;gt;no range penalty&lt;br /&gt;
| name = Ranged,&amp;lt;br&amp;gt;no range penalty&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = units_overview&lt;br /&gt;
| target_id = wiki_units_no_range_close&lt;br /&gt;
| en = Ranged,&amp;lt;br&amp;gt;no penalties&lt;br /&gt;
| name = Ranged,&amp;lt;br&amp;gt;no penalties&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Laws ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = laws_overview&lt;br /&gt;
| target_id = wiki_laws_side&lt;br /&gt;
| en = Side&lt;br /&gt;
| name = Side&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = laws_overview&lt;br /&gt;
| target_id = wiki_laws_law&lt;br /&gt;
| en = Law&lt;br /&gt;
| name = Law&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Spells ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_overview&lt;br /&gt;
| target_id = wiki_spells_school&lt;br /&gt;
| en = School&lt;br /&gt;
| name = School&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_overview&lt;br /&gt;
| target_id = wiki_spells_masterful&lt;br /&gt;
| en = Masterful&lt;br /&gt;
| name = Masterful&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_overview&lt;br /&gt;
| target_id = wiki_spells_has_masterful&lt;br /&gt;
| en = Has Masterful&amp;lt;br&amp;gt;version&lt;br /&gt;
| name = Has Masterful&amp;lt;br&amp;gt;version&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_overview&lt;br /&gt;
| target_id = wiki_spells_no_masterful&lt;br /&gt;
| en = No Masterful&amp;lt;br&amp;gt;version available&lt;br /&gt;
| name = No Masterful&amp;lt;br&amp;gt;version available&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = spells_overview&lt;br /&gt;
| target_id = wiki_spells_level&lt;br /&gt;
| en = Level&lt;br /&gt;
| name = Level&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Skills ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skill_page&lt;br /&gt;
| target_id = wiki_skills_levels&lt;br /&gt;
| en = Levels&lt;br /&gt;
| name = Levels&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skill_page&lt;br /&gt;
| target_id = wiki_skills_synergies&lt;br /&gt;
| en = Skill Synergies&lt;br /&gt;
| name = Skill Synergies&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skill_page&lt;br /&gt;
| target_id = wiki_skills_artifacts&lt;br /&gt;
| en = Artifact Effects&lt;br /&gt;
| name = Artifact Effects&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skills_overview&lt;br /&gt;
| target_id = wiki_skills_might&lt;br /&gt;
| en = Might Skills&lt;br /&gt;
| name = Might Skills&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skills_overview&lt;br /&gt;
| target_id = wiki_skills_magic&lt;br /&gt;
| en = Magic Skills&lt;br /&gt;
| name = Magic Skills&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skills_overview&lt;br /&gt;
| target_id = wiki_skills_general&lt;br /&gt;
| en = General Skills&lt;br /&gt;
| name = General Skills&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = skills_overview&lt;br /&gt;
| target_id = wiki_skills_faction&lt;br /&gt;
| en = Faction Skills&lt;br /&gt;
| name = Faction Skills&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Heroes Overview ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_overview&lt;br /&gt;
| target_id = wiki_heroes_hero&lt;br /&gt;
| en = Hero&lt;br /&gt;
| name = Hero&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_overview&lt;br /&gt;
| target_id = wiki_heroes_type&lt;br /&gt;
| en = Type&lt;br /&gt;
| name = Type&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_overview&lt;br /&gt;
| target_id = wiki_heroes_class&lt;br /&gt;
| en = Class&lt;br /&gt;
| name = Class&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_overview&lt;br /&gt;
| target_id = wiki_heroes_specialty&lt;br /&gt;
| en = Specialty&lt;br /&gt;
| name = Specialty&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = heroes_overview&lt;br /&gt;
| target_id = wiki_heroes_toggle&lt;br /&gt;
| en = Toggle specialty&lt;br /&gt;
| name = Toggle specialty&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Module:ArtifactsOverview ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_artifact&lt;br /&gt;
| en = Artifact&lt;br /&gt;
| name = Artifact&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_upgrade&lt;br /&gt;
| en = Upgrade&lt;br /&gt;
| name = Upgrade&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_upgrade_cost&lt;br /&gt;
| en = Upgrade cost&lt;br /&gt;
| name = Upgrade cost&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_slot&lt;br /&gt;
| en = Slot&lt;br /&gt;
| name = Slot&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_rarity&lt;br /&gt;
| en = Rarity&lt;br /&gt;
| name = Rarity&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_set&lt;br /&gt;
| en = Set&lt;br /&gt;
| name = Set&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_armor&lt;br /&gt;
| en = Armor&lt;br /&gt;
| name = Armor&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_back&lt;br /&gt;
| en = Back&lt;br /&gt;
| name = Back&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_belt&lt;br /&gt;
| en = Belt&lt;br /&gt;
| name = Belt&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_boots&lt;br /&gt;
| en = Boots&lt;br /&gt;
| name = Boots&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_head&lt;br /&gt;
| en = Head&lt;br /&gt;
| name = Head&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_item_slot&lt;br /&gt;
| en = Item&lt;br /&gt;
| name = Item&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_left_hand&lt;br /&gt;
| en = Main Hand&lt;br /&gt;
| name = Main Hand&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_right_hand&lt;br /&gt;
| en = Off Hand&lt;br /&gt;
| name = Off Hand&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_ring&lt;br /&gt;
| en = Rings&lt;br /&gt;
| name = Rings&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_unic_slot&lt;br /&gt;
| en = Relic&lt;br /&gt;
| name = Relic&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_common&lt;br /&gt;
| en = Common&lt;br /&gt;
| name = Common&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_rare&lt;br /&gt;
| en = Rare&lt;br /&gt;
| name = Rare&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_epic&lt;br /&gt;
| en = Epic&lt;br /&gt;
| name = Epic&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_legendary&lt;br /&gt;
| en = Legendary&lt;br /&gt;
| name = Legendary&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_special&lt;br /&gt;
| en = Special&lt;br /&gt;
| name = Special&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_items&lt;br /&gt;
| en = items:&lt;br /&gt;
| name = items:&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_toggle&lt;br /&gt;
| en = Toggle lore text&lt;br /&gt;
| name = Toggle lore text&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_no_upgrade&lt;br /&gt;
| en = Cannot be upgraded.&lt;br /&gt;
| name = Cannot be upgraded.&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_no_set&lt;br /&gt;
| en = Not part of a set.&lt;br /&gt;
| name = Not part of a set.&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = artifacts_overview&lt;br /&gt;
| target_id = wiki_ao_back_to_top&lt;br /&gt;
| en = back to top&lt;br /&gt;
| name = back to top&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== Template:UnitInfobox/base ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_upgrade_cost&lt;br /&gt;
| en = Upgrade cost&lt;br /&gt;
| name = Upgrade cost&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_growth&lt;br /&gt;
| en = Base growth&lt;br /&gt;
| name = Base growth&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_squad&lt;br /&gt;
| en = Squad value&lt;br /&gt;
| name = Squad value&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_stats&lt;br /&gt;
| en = Stats&lt;br /&gt;
| name = Stats&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_other&lt;br /&gt;
| en = Other Info&lt;br /&gt;
| name = Other Info&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_dwelling&lt;br /&gt;
| en = Dwelling&lt;br /&gt;
| name = Dwelling&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_bank&lt;br /&gt;
| en = Creature bank&lt;br /&gt;
| name = Creature bank&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_specialist&lt;br /&gt;
| en = Specialist hero&lt;br /&gt;
| name = Specialist hero&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_related&lt;br /&gt;
| en = Related Units&lt;br /&gt;
| name = Related Units&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_base&lt;br /&gt;
| en = Base unit&lt;br /&gt;
| name = Base unit&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_upgrade_a&lt;br /&gt;
| en = Upgrade A&lt;br /&gt;
| name = Upgrade A&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = unit_infobox&lt;br /&gt;
| target_id = wiki_ubox_upgrade_b&lt;br /&gt;
| en = Upgrade B&lt;br /&gt;
| name = Upgrade B&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--############################################--&amp;gt;&lt;br /&gt;
== [[Template:StatDiff]] ==&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = stat_diff&lt;br /&gt;
| target_id = wiki_diff_higher&lt;br /&gt;
| en = This stat is higher compared to the base unit.&lt;br /&gt;
| name = This stat is higher compared to the base unit.&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = stat_diff&lt;br /&gt;
| target_id = wiki_diff_lower&lt;br /&gt;
| en = This stat is lower compared to the base unit.&lt;br /&gt;
| name = This stat is lower compared to the base unit.&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = stat_diff&lt;br /&gt;
| target_id = wiki_diff_base&lt;br /&gt;
| en = Base:&lt;br /&gt;
| name = Base:&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = stat_diff&lt;br /&gt;
| target_id = wiki_diff_increase&lt;br /&gt;
| en = Increase:&lt;br /&gt;
| name = Increase:&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!------------------------------------------------&amp;gt;&lt;br /&gt;
{{WikiTranslation | subtype = stat_diff&lt;br /&gt;
| target_id = wiki_diff_decrease&lt;br /&gt;
| en = Decrease:&lt;br /&gt;
| name = Decrease:&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Category: Wiki Translations]]&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview&amp;diff=30540</id>
		<title>Module:UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview&amp;diff=30540"/>
		<updated>2026-07-20T09:30:53Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Change translation key&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    wiki_units_weekly =     &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 200&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
            units[id].units_weekly = growths[id:gsub(&#039;_alt$&#039;, &#039;&#039;):gsub(&#039;_upg$&#039;, &#039;&#039;)]&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.wiki_units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview&amp;diff=30539</id>
		<title>Module:UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview&amp;diff=30539"/>
		<updated>2026-07-20T09:25:24Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Added weekly growth&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 200&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
            units[id].units_weekly = growths[id:gsub(&#039;_alt$&#039;, &#039;&#039;):gsub(&#039;_upg$&#039;, &#039;&#039;)]&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30538</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30538"/>
		<updated>2026-07-20T09:24:25Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 200&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
            units[id].units_weekly = growths[id:gsub(&#039;_alt$&#039;, &#039;&#039;):gsub(&#039;_upg$&#039;, &#039;&#039;)]&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30537</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30537"/>
		<updated>2026-07-20T09:23:09Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 200&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
            local baseid = id:gsub(&#039;_alt$&#039;, &#039;&#039;):gsub(&#039;_upg$&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
		units[id].units_weekly = growths[baseid]&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30536</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30536"/>
		<updated>2026-07-20T09:20:11Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 200&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
		units[id].units_weekly = growths[id:gsub(&#039;_alt$&#039;, &#039;&#039;):gsub(&#039;_upg$&#039;, &#039;&#039;)]&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30535</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30535"/>
		<updated>2026-07-20T09:12:08Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 200&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
		units[id].units_weekly = growths[id:gsub(&#039;_upg(_alt)?$&#039;, &#039;&#039;)]&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30534</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30534"/>
		<updated>2026-07-20T09:04:54Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Undo revision 30533 by 76561198199147647 (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 100&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
		units[id].units_weekly = growths[id]&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30533</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30533"/>
		<updated>2026-07-20T09:04:03Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 100&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
		units[id].units_weekly = growths[base_sid]&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30532</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30532"/>
		<updated>2026-07-20T08:56:26Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid, units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 100&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
		units[id].units_weekly = growths[id]&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30531</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30531"/>
		<updated>2026-07-20T08:53:20Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = mw.ext.cargo.query(&lt;br /&gt;
		&#039;MapObject&#039;,&lt;br /&gt;
		&#039;units_hire_sid&#039;, &#039;units_weekly&#039;,&lt;br /&gt;
		{&lt;br /&gt;
			where = &#039;units_weekly &amp;gt; 0&#039;,&lt;br /&gt;
			limit = 100&lt;br /&gt;
		}&lt;br /&gt;
    )&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
		units[id].units_weekly = growths[id]&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview/doc&amp;diff=30525</id>
		<title>Module:Sandbox/Pillow/UnitsOverview/doc</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview/doc&amp;diff=30525"/>
		<updated>2026-07-20T08:47:52Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Created page with &amp;quot;{{#invoke:Sandbox/Pillow/UnitsOverview|display}}&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{#invoke:Sandbox/Pillow/UnitsOverview|display}}&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30524</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30524"/>
		<updated>2026-07-20T08:46:11Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: -&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function queryGrowths()&lt;br /&gt;
    local results = runQuery([[&lt;br /&gt;
       tables =&lt;br /&gt;
           MapObject = M&lt;br /&gt;
       | where =&lt;br /&gt;
           M.units_weekly &amp;gt; 0&lt;br /&gt;
       | fields =&lt;br /&gt;
           M.units_hire_sid = units_hire_sid,&lt;br /&gt;
           M.units_weekly = units_weekly&lt;br /&gt;
       | limit =&lt;br /&gt;
           100&lt;br /&gt;
   ]])&lt;br /&gt;
   &lt;br /&gt;
   local growths = {}&lt;br /&gt;
&lt;br /&gt;
   for _, row in ipairs(results) do&lt;br /&gt;
       growths[row.units_hire_sid] = row.units_weekly&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
   return growths&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, growths, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
		units[id].units_weekly = growths[id]&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local growth = queryGrowths()&lt;br /&gt;
    local units = consolidateResults(results, growth, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30522</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30522"/>
		<updated>2026-07-20T08:35:27Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local units = consolidateResults(results, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview&amp;diff=30519</id>
		<title>Module:UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview&amp;diff=30519"/>
		<updated>2026-07-20T08:15:40Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Undo revision 30516 by 76561198199147647 (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 4 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    -- 5 to 12 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 13 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 4 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        -- 5 to 12 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 13 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local units = consolidateResults(results, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30518</id>
		<title>Module:Sandbox/Pillow/UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:Sandbox/Pillow/UnitsOverview&amp;diff=30518"/>
		<updated>2026-07-20T08:11:52Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Created page with &amp;quot;local p = {}  local FACTION_ORDER = {     human = 1,     undead = 2,     nature = 3,     demon = 4,     unfrozen = 5,     dungeon = 6,     neutral = 7, }  local FACTION_MAPPING = {     Temple = &amp;#039;human&amp;#039;,     Necropolis = &amp;#039;undead&amp;#039;,     Grove = &amp;#039;nature&amp;#039;,     Hive = &amp;#039;demon&amp;#039;,     Schism = &amp;#039;unfrozen&amp;#039;,     Dungeon = &amp;#039;dungeon&amp;#039;,     Neutral = &amp;#039;neutral&amp;#039; }  -- Note: The right side is just a fallback, it will seldom be used. local TRANSLATION_IDS = {     creature =              &amp;#039;Cre...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.units_weekly=units_weekly, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local units = consolidateResults(results, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
	<entry>
		<id>https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview&amp;diff=30516</id>
		<title>Module:UnitsOverview</title>
		<link rel="alternate" type="text/html" href="https://wiki.hoodedhorse.com/Heroes_of_Might_and_Magic_Olden_Era/index.php?title=Module:UnitsOverview&amp;diff=30516"/>
		<updated>2026-07-20T08:05:34Z</updated>

		<summary type="html">&lt;p&gt;76561198199147647: Added weekly growth&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;local p = {}&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;
    neutral = 7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FACTION_MAPPING = {&lt;br /&gt;
    Temple = &#039;human&#039;,&lt;br /&gt;
    Necropolis = &#039;undead&#039;,&lt;br /&gt;
    Grove = &#039;nature&#039;,&lt;br /&gt;
    Hive = &#039;demon&#039;,&lt;br /&gt;
    Schism = &#039;unfrozen&#039;,&lt;br /&gt;
    Dungeon = &#039;dungeon&#039;,&lt;br /&gt;
    Neutral = &#039;neutral&#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;
    creature =              &#039;Creature&#039;,&lt;br /&gt;
    faction =               &#039;Faction&#039;,&lt;br /&gt;
    tier =                  &#039;Tier&#039;,&lt;br /&gt;
    wiki_cost =             &#039;Cost&#039;,&lt;br /&gt;
    units_weekly =          &#039;Growth&#039;,&lt;br /&gt;
    hp =                    &#039;Hp&#039;,&lt;br /&gt;
    offence =               &#039;Offence&#039;,&lt;br /&gt;
    defence =               &#039;Defence&#039;,&lt;br /&gt;
    damage =                &#039;Damage&#039;,&lt;br /&gt;
    initiative =            &#039;Initiative&#039;,&lt;br /&gt;
    morale =                &#039;Morale&#039;,&lt;br /&gt;
    luck =                  &#039;Luck&#039;,&lt;br /&gt;
    speed =                 &#039;Speed&#039;,&lt;br /&gt;
    wiki_units_neutral =    &#039;Neutral&#039;,&lt;br /&gt;
    wiki_units_attack_type = &#039;Attack type&#039;,&lt;br /&gt;
    melee_attack =          &#039;Melee Attack&#039;, -- melee_attack = 1&lt;br /&gt;
    wiki_units_no_counter = &#039;Melee,&amp;lt;br&amp;gt;no counter&#039;, -- melee_attack_no_counter = 2&lt;br /&gt;
    remote_attack         = &#039;Long Reach&#039;, -- remote_attack = 3&lt;br /&gt;
    ranged_attack         = &#039;Ranged Attack&#039;, -- ranged_attack = 4&lt;br /&gt;
    wiki_units_no_close = &#039;Ranged,&amp;lt;br&amp;gt;no melee penalty&#039;, -- ranged_attack_no_close = 5&lt;br /&gt;
    wiki_units_no_range = &#039;Ranged,&amp;lt;br&amp;gt;no range penalty&#039;, -- ranged_attack_no_range = 6&lt;br /&gt;
    wiki_units_no_range_close = &#039;Ranged,&amp;lt;br&amp;gt;no penalties&#039;, -- ranged_attack_no_range_close = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local RESOURCE_ICONS = {&lt;br /&gt;
    wood_cost     = &#039;Wood&#039;,&lt;br /&gt;
    ore_cost      = &#039;Ore&#039;,&lt;br /&gt;
    mercury_cost  = &#039;Mercury&#039;,&lt;br /&gt;
    dust_cost     = &#039;Dust&#039;,&lt;br /&gt;
    crystal_cost  = &#039;Crystal&#039;,&lt;br /&gt;
    gemstone_cost = &#039;Gemstones&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local STAT_ICONS = {&lt;br /&gt;
    hp          = &#039;Icon_Stats_Health&#039;,&lt;br /&gt;
    offence     = &#039;Icon_Stats_Attack&#039;,&lt;br /&gt;
    defence     = &#039;Icon_Stats_Defence&#039;,&lt;br /&gt;
    damage      = &#039;Icon_Stats_Damage&#039;,&lt;br /&gt;
    morale      = &#039;Icon_Stats_Morale&#039;,&lt;br /&gt;
    luck        = &#039;Icon_Stats_Luck&#039;,&lt;br /&gt;
    initiative  = &#039;Icon_Stats_Initiative&#039;,&lt;br /&gt;
    speed       = &#039;Icon_Stats_Speed&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_RANKS = {&lt;br /&gt;
    base_passive_melee_attack_name = 1,                 -- Swordsman     100,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = 2,      -- Vampire Lord  101,   0,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_remote_attack_name = 3,                -- Graverobber     0, 100,   0,   0,   0,   0,   0,   0,   0&lt;br /&gt;
    base_passive_ranged_attack_name = 4,                -- Onyx Dancer    50, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = 5,       -- Apotheosis    100, 100, 100,  90,  80,  70,  60,  50,  50&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = 6,       -- Marksman       50, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = 7, -- Halfling      100, 100, 100, 100, 100, 100, 100, 100, 100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ATTACK_ICONS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;Base_passive_melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;Base_passive_noncounter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;Base_passive_remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;Base_passive_ranged_attack&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;Base passive duelist&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;Base_passive_sharpshooter&#039;, -- TODO: clarify icon&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;Arbitrator_passive_1&#039;, -- TODO: clarify icon&lt;br /&gt;
}&lt;br /&gt;
local ATTACK_WORDS = {&lt;br /&gt;
    base_passive_melee_attack_name = &#039;melee_attack&#039;,&lt;br /&gt;
    base_passive_melee_attack_no_counter_name = &#039;wiki_units_no_counter&#039;,&lt;br /&gt;
    base_passive_remote_attack_name = &#039;remote_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_name = &#039;ranged_attack&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_close_name = &#039;wiki_units_no_close&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_name = &#039;wiki_units_no_range&#039;,&lt;br /&gt;
    base_passive_ranged_attack_no_range_close_name = &#039;wiki_units_no_range_close&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local ROMAN = { &#039;I&#039;, &#039;II&#039;, &#039;III&#039;, &#039;IV&#039;, &#039;V&#039;, &#039;VI&#039;, &#039;VII&#039;, &#039;VIII&#039; }&lt;br /&gt;
&lt;br /&gt;
local PAGES = {&lt;br /&gt;
    Stinger = &#039;Stinger_(unit)&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local MORALE_AND_LUCK = {&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated1&amp;quot;&amp;gt;1&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated2&amp;quot;&amp;gt;2&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
    &#039;&amp;lt;span class=&amp;quot;elevated3&amp;quot;&amp;gt;3&amp;lt;/span&amp;gt;&#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Awards a number from 0 to 3 for a unit, based on how upgraded it is&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeUpgradeRank(row)&lt;br /&gt;
    local base = (row[&#039;base&#039;] and row[&#039;base&#039;] ~= &amp;quot;&amp;quot;) and 1 or 0&lt;br /&gt;
    local upgrade = (row[&#039;upgrade&#039;] and row[&#039;upgrade&#039;] ~= &amp;quot;&amp;quot;) and 10 or 0&lt;br /&gt;
    local sum = base + upgrade&lt;br /&gt;
    if sum == 1 then&lt;br /&gt;
        return 3&lt;br /&gt;
    elseif sum == 10 then&lt;br /&gt;
        return 1&lt;br /&gt;
    elseif sum == 0 then&lt;br /&gt;
        return 0&lt;br /&gt;
    else&lt;br /&gt;
        return 2&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns one of the ATTACK_RANKS keys (e.g. &#039;base_passive_melee_attack_name&#039;) or an empty string&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeAttackType(row)&lt;br /&gt;
    local abilities = row[&#039;shared_abilities&#039;]&lt;br /&gt;
    if type(abilities) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    for key, _ in pairs(ATTACK_RANKS) do&lt;br /&gt;
        local pattern = &amp;quot;%f[%w]&amp;quot; .. key .. &amp;quot;%f[%W]&amp;quot;&lt;br /&gt;
        if string.match(abilities, pattern) then&lt;br /&gt;
            return key&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Returns a rich wikitext with numbers and icons&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function computeCost(row)&lt;br /&gt;
    local gold = row[&#039;gold_cost&#039;]&lt;br /&gt;
    local cost = gold .. &#039;[[File:Icon_Resource_Gold.png|24px|link=]]&#039;&lt;br /&gt;
    for key, icon in pairs(RESOURCE_ICONS) do&lt;br /&gt;
        local val = row[key]&lt;br /&gt;
        if val and val ~= &#039;&#039; and val ~= 0 then&lt;br /&gt;
            cost = cost .. &#039; &#039; .. val .. &#039;[[File:Icon_Resource_&#039; .. icon .. &#039;.png|24px|link=]]&#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return cost&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Retrieves the data from Cargo&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function query(lang, forcedFaction)&lt;br /&gt;
    local tables = &#039;Unit, Translation&#039;&lt;br /&gt;
    local fields = &#039;&#039; ..&lt;br /&gt;
        &#039;Unit.id=id, &#039; ..&lt;br /&gt;
        &#039;Unit.faction=faction, &#039; ..&lt;br /&gt;
        &#039;Unit.tier=tier, &#039; ..&lt;br /&gt;
        &#039;Unit.units_weekly=units_weekly, &#039; ..&lt;br /&gt;
        &#039;Unit.hp=hp, &#039; ..&lt;br /&gt;
        &#039;Unit.offence=offence, &#039; ..&lt;br /&gt;
        &#039;Unit.defence=defence, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_min=damage_min, &#039; ..&lt;br /&gt;
        &#039;Unit.damage_max=damage_max, &#039; ..&lt;br /&gt;
        &#039;Unit.morale=morale, &#039; ..&lt;br /&gt;
        &#039;Unit.luck=luck, &#039; ..&lt;br /&gt;
        &#039;Unit.initiative=initiative, &#039; ..&lt;br /&gt;
        &#039;Unit.speed=speed, &#039; ..&lt;br /&gt;
        &#039;Unit.shared_abilities=shared_abilities, &#039; .. -- we use it for range detection&lt;br /&gt;
        -- costs&lt;br /&gt;
        &#039;Unit.gold_cost=gold_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.wood_cost=wood_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.ore_cost=ore_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.mercury_cost=mercury_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.dust_cost=dust_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.crystal_cost=crystal_cost, &#039; ..&lt;br /&gt;
        &#039;Unit.gemstone_cost=gemstone_cost, &#039; ..&lt;br /&gt;
        -- upgrade&lt;br /&gt;
        &#039;Unit.base_sid=base, &#039; ..&lt;br /&gt;
        &#039;Unit.upgrade_sid=upgrade, &#039; ..&lt;br /&gt;
        -- Translation&lt;br /&gt;
        &#039;Translation.language=language, &#039; ..&lt;br /&gt;
        &#039;Translation.name=transName&#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, &#039;Unit.faction=&amp;quot;&#039; .. forcedFaction .. &#039;&amp;quot;&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    table.insert(where, &#039;(Translation.language = &amp;quot;en&amp;quot; OR Translation.language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;)&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Get BOTH the English row AND the target language row&lt;br /&gt;
    local cargoArgs = {&lt;br /&gt;
        join = &#039;Unit.id = Translation.target_id&#039;,&lt;br /&gt;
        where = table.concat(where, &#039; AND &#039;),&lt;br /&gt;
        orderBy = &#039;Unit.id ASC&#039;,&lt;br /&gt;
        limit = 1000 -- Increased limit since each unit will return 2 rows&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return mw.ext.cargo.query(tables, fields, cargoArgs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Scans the Cargo results and folds the EN and lang rows into a single row&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function consolidateResults(results, lang)&lt;br /&gt;
    local units = {}&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local id = row[&#039;id&#039;]&lt;br /&gt;
&lt;br /&gt;
        -- If this is the first time seeing this id, set up its basic stats&lt;br /&gt;
        if not units[id] then&lt;br /&gt;
            units[id] = mw.clone(row)&lt;br /&gt;
            units[id].upgradeRank = computeUpgradeRank(row)&lt;br /&gt;
            units[id].attackType = computeAttackType(row)&lt;br /&gt;
            units[id].cost = computeCost(row)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Save the names based on which language row we are currently looping over&lt;br /&gt;
        if row[&#039;language&#039;] == &#039;en&#039; then&lt;br /&gt;
            units[id].nameEn = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
        if row[&#039;language&#039;] == lang then&lt;br /&gt;
            units[id].nameX = row[&#039;transName&#039;]&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Convert the Dictionary into an Array&lt;br /&gt;
    local unitsArray = {}&lt;br /&gt;
    for _, unitData in pairs(units) do&lt;br /&gt;
        table.insert(unitsArray, unitData)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitsArray&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Decides the order between two units, for sorting purposes.&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function sortUnits(unitA, unitB)&lt;br /&gt;
    local rankA = FACTION_ORDER[unitA.faction] or 99&lt;br /&gt;
    local rankB = FACTION_ORDER[unitB.faction] or 99&lt;br /&gt;
&lt;br /&gt;
    if rankA ~= rankB then&lt;br /&gt;
        return rankA &amp;lt; rankB&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.tier ~= unitB.tier then&lt;br /&gt;
        return unitA.tier &amp;lt; unitB.tier&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if unitA.upgradeRank ~= unitB.upgradeRank then&lt;br /&gt;
        return unitA.upgradeRank &amp;lt; unitB.upgradeRank&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return unitA.hp &amp;lt; unitB.hp&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 = &#039;&amp;quot;&#039; .. table.concat(list, &#039;&amp;quot;, &amp;quot;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local where = {}&lt;br /&gt;
    table.insert(where, &#039;target_id IN (&#039; .. idListString .. &#039;)&#039;)&lt;br /&gt;
    table.insert(where, &#039;language = &amp;quot;&#039; .. lang .. &#039;&amp;quot;&#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(&#039;Translation&#039;, &#039;target_id, name&#039;, {&lt;br /&gt;
        where = table.concat(where, &#039; AND &#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 .. &#039;[[Data:WikiTranslations/&#039; .. lang .. &#039;#&#039; .. key .. &#039;|💬]]&#039;&lt;br /&gt;
        dictionary[key] = value .. &#039;💬&#039;&lt;br /&gt;
    end&lt;br /&gt;
    for _, row in ipairs(results) do&lt;br /&gt;
        local name = row[&#039;name&#039;]&lt;br /&gt;
        if name and name ~= &#039;&#039; and not name:find(&#039;💬&#039;) then&lt;br /&gt;
            dictionary[row[&#039;target_id&#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;
-- Displays a variable&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function dump(target)&lt;br /&gt;
    return &#039;&amp;lt;pre&amp;gt;&#039; .. mw.dumpObject(target) .. &#039;&amp;lt;/pre&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds an icon+text combo for the unit&#039;s name&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderUnitName(u, suffix)&lt;br /&gt;
    local stem = PAGES[u.nameEn] or u.nameEn&lt;br /&gt;
    local page = stem .. suffix&lt;br /&gt;
    local img = &#039;[[File:&#039; .. u.nameEn .. &#039; icon.png|64px|link=&#039; .. page .. &#039;]]&#039;&lt;br /&gt;
    local text = &#039;[[&#039; .. page .. &#039;|&#039; .. u.nameX .. &#039;]]&#039;&lt;br /&gt;
    return img .. &#039; &#039; .. text&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;
    tr:tag(&#039;th&#039;):wikitext(text):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for stat tds&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createStat(tr, value, pngName)&lt;br /&gt;
    tr:tag(&#039;td&#039;):wikitext(value .. &#039;[[File:&#039; .. pngName .. &#039;.png|link=]]&#039;):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Boilerplate for the separator&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addSeparator(htmlTable, className, content)&lt;br /&gt;
    htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;separator&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, &#039;&#039;):attr(&#039;colspan&#039;, 100):wikitext(content):done()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
    for key, _ in pairs(FACTION_ORDER) do&lt;br /&gt;
        if key == &#039;neutral&#039; then&lt;br /&gt;
            local file = &#039;[[File:Primordial_Chaos.png|24px]]&#039;&lt;br /&gt;
            words[key] = file .. &#039; [[Neutral_Units&#039; .. suffix .. &#039;|&#039;.. words.wiki_units_neutral .. &#039;]]&#039;&lt;br /&gt;
        else&lt;br /&gt;
            words[key] = frame:preprocess(&#039;{{F|&#039; .. key .. &#039;|&#039; .. lang .. &#039;}}&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function renderIconWithHint(fileName, text, frame)&lt;br /&gt;
    local file = &#039;[[File:&#039; .. fileName .. &#039;.png|32px|link=]]&#039;;&lt;br /&gt;
    return frame:preprocess(&#039;{{Hint|&#039; .. file .. &#039;|&#039; .. text .. &#039;}}&#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createHeader(htmlTable, words, frame)&lt;br /&gt;
    local header = htmlTable:tag(&#039;tr&#039;)&lt;br /&gt;
    -- 1 to 5 (general)&lt;br /&gt;
    addTh(header, words.creature)&lt;br /&gt;
    addTh(header, words.faction)&lt;br /&gt;
    addTh(header, words.tier)&lt;br /&gt;
    addTh(header, words.wiki_cost)&lt;br /&gt;
    addTh(header, words.units_weekly)&lt;br /&gt;
    -- 6 to 13 (main stats)&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.hp, words.hp, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.offence, words.offence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.defence, words.defence, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.damage, words.damage, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.morale, words.morale, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.luck, words.luck, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.initiative, words.initiative, frame))&lt;br /&gt;
    addTh(header, renderIconWithHint(STAT_ICONS.speed, words.speed, frame))&lt;br /&gt;
    -- 14 (others)&lt;br /&gt;
    addTh(header, renderIconWithHint(&#039;Sunlight_cavalry_passive_3&#039;, words.wiki_units_attack_type, frame))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
local function createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
    local currentTier = units[1].tier&lt;br /&gt;
    local currentFaction = units[1].faction&lt;br /&gt;
    for _, u in ipairs(units) do&lt;br /&gt;
&lt;br /&gt;
        -- separators&lt;br /&gt;
        if u.faction ~= currentFaction then&lt;br /&gt;
            currentFaction = u.faction&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-large&#039;, words[currentFaction])&lt;br /&gt;
        elseif u.tier ~= currentTier then&lt;br /&gt;
            currentTier = u.tier&lt;br /&gt;
            addSeparator(htmlTable, &#039;separator-tiny&#039;, &#039;&#039;)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local tr = htmlTable:tag(&#039;tr&#039;):addClass(&#039;unit&#039;)&lt;br /&gt;
        if u.upgradeRank == 2 or u.upgradeRank == 3 then&lt;br /&gt;
            tr:addClass(&#039;upgraded&#039;)&lt;br /&gt;
        end&lt;br /&gt;
        -- 1 to 5 (general)&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(renderUnitName(u, suffix)):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(words[currentFaction]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(ROMAN[tonumber(u.tier)]):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, string.match(u.cost, &amp;quot;^%d+&amp;quot;)):wikitext(u.cost):done()&lt;br /&gt;
        tr:tag(&#039;td&#039;):wikitext(tonumber(u.units_weekly)):done()&lt;br /&gt;
        -- 6 to 13 (main stats)&lt;br /&gt;
        createStat(tr, u.hp, STAT_ICONS.hp)&lt;br /&gt;
        createStat(tr, u.offence, STAT_ICONS.offence)&lt;br /&gt;
        createStat(tr, u.defence, STAT_ICONS.defence)&lt;br /&gt;
        createStat(tr, u.damage_min .. &#039;-&#039; .. u.damage_max, STAT_ICONS.damage)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.morale)] or u.morale, STAT_ICONS.morale)&lt;br /&gt;
        createStat(tr, MORALE_AND_LUCK[tonumber(u.luck)] or u.luck, STAT_ICONS.luck)&lt;br /&gt;
        createStat(tr, u.initiative, STAT_ICONS.initiative)&lt;br /&gt;
        createStat(tr, u.speed, STAT_ICONS.speed)&lt;br /&gt;
        -- 14 (others)&lt;br /&gt;
        local wording = ATTACK_WORDS[u.attackType]&lt;br /&gt;
        local icon =  renderIconWithHint(ATTACK_ICONS[u.attackType], words[wording], frame)&lt;br /&gt;
        tr:tag(&#039;td&#039;):attr(&#039;data-sort-value&#039;, ATTACK_RANKS[u.attackType]):wikitext(icon):done()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main public function&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 ~= &#039;&#039; and args.lang or &#039;en&#039;&lt;br /&gt;
    --lang = &#039;ru&#039;&lt;br /&gt;
    local forcedFaction = FACTION_MAPPING[args.faction] or nil&lt;br /&gt;
    --forcedFaction = &#039;human&#039;&lt;br /&gt;
&lt;br /&gt;
    -- Language&lt;br /&gt;
    local suffix = lang ~= &#039;en&#039; and &#039;/&#039; .. lang or &#039;&#039;&lt;br /&gt;
    local words = translateIds(TRANSLATION_IDS, lang)&lt;br /&gt;
    addFactionWords(words, lang, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Cargo&lt;br /&gt;
    local results = query(lang, forcedFaction)&lt;br /&gt;
    local units = consolidateResults(results, lang)&lt;br /&gt;
    table.sort(units, sortUnits)&lt;br /&gt;
&lt;br /&gt;
    -- Table&lt;br /&gt;
    local htmlTable = mw.html.create(&#039;table&#039;)&lt;br /&gt;
    htmlTable:addClass(&#039;wikitable sortable&#039;)&lt;br /&gt;
    if forcedFaction then&lt;br /&gt;
        htmlTable:addClass(&#039;forced-faction&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    createHeader(htmlTable, words, frame)&lt;br /&gt;
    createBody(htmlTable, units, words, suffix, frame)&lt;br /&gt;
&lt;br /&gt;
    -- Output&lt;br /&gt;
    local styleTag = frame:extensionTag(&#039;templatestyles&#039;, &#039;&#039;, { src = frame:getTitle() .. &#039;/styles.css&#039; })&lt;br /&gt;
    return &#039;&amp;lt;div class=&amp;quot;units-overview&amp;quot;&amp;gt;&#039; .. styleTag .. tostring(htmlTable) .. &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>76561198199147647</name></author>
	</entry>
</feed>