Module:Unit

From Heroes of Might and Magic: Olden Era Official Wiki
Revision as of 10:57, 6 January 2026 by Krom (talk | contribs)

Module for requesting characteristics of all existing units in Olden Era. Actual data is stored in Data:units.json.

For documentation page, see Module:Unit/doc.

Supported Commands

get

The module supports the main get command, through which you can retrieve individual unit attributes (listed below). The basic syntax of the command is:

{{#invoke:Unit|get|Unit Name|Property Name}}
Name Description Example Result
name Unit name (localized)
{{#invoke:Unit|get|Angel|name}}
Angel
Base name Name of un-upgraded unit (localized)
{{#invoke:Unit|get|Apotheosis|baseName}}
First Upgrade Name of upgraded unit (localized)
{{#invoke:Unit|get|Angel|upgradeName}}
Second Upgrade Name of alt. upgraded unit (localized)
{{#invoke:Unit|get|Angel|altUpgradeName}}
Image name Name without spaces and apostrophes, suitable for wiki file names
{{#invoke:Unit|get|Sun's Aegis|imageName}}
description Narrative description (localized with data substitutions)
{{#invoke:Unit|get|Angel|description}}
The inquisition’s greatest belief is that angels still accompany their mission. The beings that aid the Templars in combat are but a mere imitation — a human soul willingly fused with winged armor, destined to continue the fight.
faction Unit faction
{{#invoke:Unit|get|Angel|faction}}
human
tier Unit tier
{{#invoke:Unit|get|Angel|tier}}
7
id Unit ID
{{#invoke:Unit|get|Angel|id}}
angel
nativeBiome Native biome
{{#invoke:Unit|get|Angel|nativeBiome}}
Grass
cost Hiring cost
{{#invoke:Unit|get|Angel|cost}}
cost.gold
cost.resource
Access to specific property in unitCost object
{{#invoke:Unit|get|Angel|cost.gold}}
{{#invoke:Unit|get|Angel|cost.gemstones}}

upgradeCost Upgrade cost
{{#invoke:Unit|get|Archangel|upgradeCost}}
weeklyIncrement Base weekly increment from main building
{{#invoke:Unit|get|Angel|weeklyIncrement}}
1
expBonus Experience gained when defeating the unit
{{#invoke:Unit|get|Angel|expBonus}}
366
squadValue Squad value of the unit (used for army composition calculations)
{{#invoke:Unit|get|Angel|squadValue}}
3660
hp Maximum health
{{#invoke:Unit|get|Angel|hp}}
225
offence Offence parameter
{{#invoke:Unit|get|Angel|offence}}
30
defence Defence parameter
{{#invoke:Unit|get|Angel|defence}}
30
damageMin Minimum damage dealt
{{#invoke:Unit|get|Angel|damageMin}}
50
damageMax Maximum damage dealt
{{#invoke:Unit|get|Angel|damageMax}}
75
moral Default morale
{{#invoke:Unit|get|Angel|moral}}
0
moralMin Minimum morale value
{{#invoke:Unit|get|Angel|moralMin}}
-3
moralMax Maximum morale value
{{#invoke:Unit|get|Angel|moralMax}}
3
luck Default luck
{{#invoke:Unit|get|Angel|luck}}
0
luckMin Minimum luck value
{{#invoke:Unit|get|Angel|luckMin}}
-3
luckMax Maximum luck value
{{#invoke:Unit|get|Angel|luckMax}}
3
initiative Initiative
{{#invoke:Unit|get|Angel|initiative}}
8
speed Speed
{{#invoke:Unit|get|Angel|speed}}
4
moveType Movement type (fly or teleport)
{{#invoke:Unit|get|Angel|moveType}}
fly
baseClass.name Base class name (localized)
{{#invoke:Unit|get|Angel|baseClass.name|ru}}
Embodiment
baseClass.description Base class description (localized)
{{#invoke:Unit|get|Angel|baseClass.description}}
A spirit, soul, or other intangible power that has coalesced into material form.

Morale range: –3 to -3.
Luck range: –3 to -3.

Fallen Embodiments do NOT count towards the power of Necromancy reanimation after battle.
baseClass.icon Base class icon filename
{{#invoke:Unit|get|Angel|baseClass.icon}} or [[File:{{#invoke:Unit|get|Angel|baseClass.icon}}|50px]]
Base class embodiment.png -

Active Abilities

To access specific active ability properties, use the format: abilities.index.property

Name Description Example Result
abilities.1.name Ability name (localized)
{{#invoke:Unit|get|Flaming Phoenix|abilities.1.name|ru}}
Пробуждение. Огонь II
abilities.1.description Ability main description (localized with data substitutions)
{{#invoke:Unit|get|Flaming Phoenix|abilities.1.description|ru}}
Получает +1 к скорости и инициативе, +25 к здоровью и всегда наносит максимальный урон. Позволяет использовать остальные способности. Применяется один раз за бой и действует до конца боя.
abilities.1.infoDescription Additional ability information (localized)
{{#invoke:Unit|get|Flaming Phoenix|abilities.1.infoDescription|ru}}
Не завершает ход.
abilities.1.excaptionInTooltip Ability restrictions or exceptions (localized)
{{#invoke:Unit|get|Cultist|abilities.1.excaptionInTooltip|ru}}
Не действует на магических существ, нежить и конструкты
abilities.1.abilityType Type of ability (localized)
{{#invoke:Unit|get|Flaming Phoenix|abilities.1.abilityType|ru}}
Особая способность
abilities.1.ability_tier Ability rank or tier
{{#invoke:Unit|get|Flaming Phoenix|abilities.1.ability_tier|ru}}
6
abilities.1.energyLevel Energy required for ability activation
{{#invoke:Unit|get|Flaming Phoenix|abilities.1.energyLevel|ru}}
3
abilities.1.icon Ability icon filename
{{#invoke:Unit|get|Flaming Phoenix|abilities.1.icon|ru}}
Phoenix_ability_2.png

Passive Abilities

To access specific passive ability properties, use the format: passives.index.property

Name Description Example Result
passives.1.name Passive ability name (localized)
{{#invoke:Unit|get|Giant Toad|passives.1.name}}
Melee Attack
passives.1.description Passive ability description (localized with data substitutions)
{{#invoke:Unit|get|Giant Toad|passives.1.description}}
Can only attack adjacent enemies. Provokes counterattacks.
passives.1.excaptionInTooltip Passive ability restrictions or exceptions (localized)
{{#invoke:Unit|get|Giant Toad|passives.1.excaptionInTooltip}}
passives.1.icon Passive ability icon filename
{{#invoke:Unit|get|Giant Toad|passives.1.icon}}
Base passive melee attack.png

Alternative Attacks

To access specific alternative attack properties, use the format: alternativeAttacks.index.property

Name Description Example Result
alternativeAttacks.1.name Alternative attack name (localized)
{{#invoke:Unit|get|Infiltrator|alternativeAttacks.1.name}}
Fighting Style: Hit and Run
alternativeAttacks.1.description Alternative attack description (localized with data substitutions)
{{#invoke:Unit|get|Infiltrator|alternativeAttacks.1.description}}
This creature will return to its original position after attacking, but deal –50% Damage.
alternativeAttacks.1.abilityType Type of alternative attack (localized)
{{#invoke:Unit|get|Infiltrator|alternativeAttacks.1.abilityType}}
Alternative Attack
alternativeAttacks.1.infoDescription Additional alternative attack information (localized)
{{#invoke:Unit|get|Infiltrator|alternativeAttacks.1.infoDescription}}
Does not spend Focus Charges.
alternativeAttacks.1.icon Alternative attack icon filename
{{#invoke:Unit|get|Infiltrator|alternativeAttacks.1.icon}}
Assassin_ability_1.png
alternativeAttacks.1.ability_tier Alternative attack rank or tier
{{#invoke:Unit|get|Infiltrator|alternativeAttacks.1.ability_tier}}
1

Localization Support

The module supports localization in multiple languages. You can specify the language as an optional third parameter:

{{#invoke:Unit|get|Unit Name|Property Name|Language Code}}

For example:

  • {{#invoke:Unit|get|Angel|name|pl}}
    Anioł - Polish
  • {{#invoke:Unit|get|Angel|name|ru}}
    Ангел - Russian

local Unit = {}

-- Cache for data
local dataCache = nil

-- Load localization module
local LangStrings = require("Module:LocGetString")

-- Function to load JSON data from wiki page
local function loadJson(title)
    local page = mw.title.new(title)
    local content = page and page:getContent() or ""
    if content == "" then return {} end
    local ok, data = pcall(mw.text.jsonDecode, content)
    if not ok then return {} end
    return data
end

-- Load unit data from unitdata_work.json
local function loadUnits()
    if dataCache then return dataCache end
    dataCache = {}

    -- Load main JSON file (in real wiki, JSON data needs to be placed on this page)
    local units = loadJson("Data:units.json") -- or another suitable name for wiki page

    -- Copy data to cache
    for name, unit in pairs(units) do
        dataCache[name] = unit
    end

    return dataCache
end

-- Function to get localized text
local function getLocalizedText(sid, lang, returnEmptyIfMissing)
    if sid and sid ~= "" then
        local result = LangStrings.getText({args = {sid = sid, lang = lang}})
        return result
    else
        if returnEmptyIfMissing then
            return ""
        else
            return sid  -- Return the original value if no sid
        end
    end
end

-- Function to get localized description with data substitutions
local function getLocalizedDescription(item, lang)
    if not item or not item.sid_desc then
        return ""
    end

    local text = LangStrings.getText({ args = { sid = item.sid_desc, lang = lang } }) or ""
    local data = item.data or {}

    local replacements = {}

    -- If there are data_sid, localize each element and substitute data
    if item.data_sid and type(item.data_sid) == "table" then
        for i, sid in ipairs(item.data_sid) do
            if sid and sid ~= "" then
                local part = LangStrings.getText({ args = { sid = sid, lang = lang } }) or ""
                if part ~= "" then
                    -- If data[i] is a table (array of values)
                    if type(data[i]) == "table" then
                        -- Replace placeholders {0}, {1}, {2}, etc. with data from data[i]
                        part = part:gsub("{(%d+)}", function(n)
                            local idx = tonumber(n) + 1  -- +1 because Lua indices start with 1
                            local val = data[i][idx]
                            return val ~= nil and tostring(val) or "{" .. n .. "}"
                        end)
                        replacements[i] = part
                    else
                        -- If data[i] is a single value, replace only {0}
                        part = part:gsub("{0}", tostring(data[i]))
                        replacements[i] = part
                    end
                end
            elseif data[i] ~= nil then
                replacements[i] = tostring(data[i])
            end
        end

        -- Add remaining elements from data that are not in data_sid
        for i = #item.data_sid + 1, #data do
            if data[i] ~= nil then
                replacements[i] = tostring(data[i])
            end
        end
    else
        -- If no data_sid, use data directly
        for i, val in ipairs(data) do
            replacements[i] = tostring(val)
        end
    end

    -- Substitute replacements in the base text
    text = text:gsub("{(%d+)}", function(n)
        local idx = tonumber(n) + 1
        local val = replacements[idx]
        return val or "{" .. n .. "}"
    end)

    return text
end

-- Function to get unit name using localization
local function getUnitName(unit, lang)
    local id = unit.id or ""
    if id ~= "" then
        return getLocalizedText(id .. "_name", lang, false)
    else
        return unit.name or "No name"
    end
end

-- Function to get unit property
function Unit.get(frame)
    local args = frame.args
    local unitName = args[1] or args.unit or ""
    local property = args[2] or args.property or ""
    local lang = args[3] or args.lang or "en"  -- Get language parameter, default to 'en'

    unitName = mw.text.trim(unitName or "")
    property = mw.text.trim(property or "")
    lang = mw.text.trim(lang or "en")

    if unitName == "" then
        return "Unit name not specified"
    end

    if property == "" then
        return "Property not specified"
    end

    -- Load unit data
    local units = loadUnits()

    -- Check if unit exists in data
    local unit = units[unitName]
    if not unit then
        return "Unit '" .. unitName .. "' not found"
    end

    -- If property is 'name', return localized name
    if property == "name" then
        return getUnitName(unit, lang)
    end

    -- Check if property is in format "arrayName.index.propertyName" (for accessing arrays)
    local arrayAccess = string.match(property, "^(.+)%.(%d+)%.(.+)$")
    if arrayAccess then
        local arrayName, indexStr, propName = string.match(property, "^(.+)%.(%d+)%.(.+)$")
        local index = tonumber(indexStr)
        if arrayName and index and propName then
            local array = unit[arrayName]

            -- Check if the array exists and is a table
            if array == nil then
                return ""  -- Return empty string if array not found
            end

            if type(array) ~= "table" then
                return ""  -- Return empty string if property is not an array
            end

            -- Use 1-based indexing as per Lua convention
            local element = array[index]

            -- Now check if we found an element
            if element ~= nil and type(element) == "table" then
                local value = element[propName]
                -- Special handling for 'name' property - use localization if sid_name exists (higher priority)
                if propName == "name" and element["sid_name"] then
                    return getLocalizedText(element["sid_name"], lang, false)
                elseif value ~= nil then
                    -- Handle special localized fields that should return empty string if missing
                    if propName == "abilityType" or propName == "excaptionInTooltip" or propName == "infoDescription" then
                        return getLocalizedText(value, lang, true)
                    else
                        return tostring(value)
                    end
                else
                    -- If property is 'description', return localized description with data substitutions
                    if propName == "description" and element["sid_desc"] then
                        return getLocalizedDescription(element, lang)
                    else
                        return ""  -- Return empty string if property not found
                    end
                end
            else
                return ""  -- Return empty string if array element not found
            end
        end
    end

    -- Check if property is in format "baseClass.propertyName" (for accessing baseClass object)
    local baseClassAccess = string.match(property, "^baseClass%.(.+)$")
    if baseClassAccess then
        local baseClass = unit["baseClass"]
        if baseClass == nil then
            return ""  -- Return empty string if baseClass not found
        end

        if type(baseClass) ~= "table" then
            return ""  -- Return empty string if baseClass is not a table
        end

        local propName = baseClassAccess
        local value = baseClass[propName]
        -- Special handling for 'name' property - use localization if sid_name exists (higher priority)
        if propName == "name" and baseClass["sid_name"] then
            return getLocalizedText(baseClass["sid_name"], lang, false)
        elseif value ~= nil then
            -- Handle special localized fields that should return empty string if missing
            if propName == "abilityType" or propName == "excaptionInTooltip" or propName == "infoDescription" then
                return getLocalizedText(value, lang, true)
            else
                return tostring(value)
            end
        else
            -- If property is 'description', return localized description with data substitutions
            if propName == "description" and baseClass["sid_desc"] then
                return getLocalizedDescription(baseClass, lang)
            else
                return ""  -- Return empty string if property not found in baseClass
            end
        end
    end

    -- Check if property is in format "unitCost.propertyName" or "cost.propertyName" (for accessing unitCost object)
    local match1, match2 = string.match(property, "^(unitCost|cost)%.(.+)$")
    if match1 and match2 then
        local unitCost = unit["unitCost"]
        if unitCost == nil then
            return ""  -- Return empty string if unitCost not found
        end

        if type(unitCost) ~= "table" then
            return ""  -- Return empty string if unitCost is not a table
        end

        local propName = match2
        local value = unitCost[propName]

        if value ~= nil then
            return tostring(value)
        else
            return ""  -- Return empty string if property not found in unitCost
        end
    end

    -- First check if property exists in main unit object
    if unit[property] ~= nil then
        -- Special handling for 'name' property - use localization if sid_name exists (higher priority)
        if property == "name" and unit["sid_name"] then
            return getLocalizedText(unit["sid_name"], lang, false)
        -- Special handling for 'description' property - use localization if sid_desc exists
        elseif property == "description" and unit["sid_desc"] then
            return getLocalizedDescription(unit, lang)
        else
            return tostring(unit[property])
        end
    end

    -- Special handling for 'name' and 'description' properties when not in main unit object
    if property == "name" then
        -- Use id to get localized name if sid_name doesn't exist
        local id = unit.id or ""
        if id ~= "" then
            return getLocalizedText(id .. "_name", lang, false)
        else
            return unit.name or "No name"
        end
    elseif property == "description" then
        -- Use id to get localized description if sid_desc doesn't exist
        local id = unit.id or ""
        if id ~= "" then
            return getLocalizedText(id .. "_narrativeDescription", lang, true)
        end
        -- Return empty string if no description is available
    end

    -- Then check if property exists in stats
    if unit.stats and unit.stats[property] ~= nil then
        return tostring(unit.stats[property])
    end

    -- If property not found
    return ""
end

return Unit