Module:Unit: Difference between revisions

From Heroes of Might and Magic: Olden Era Official Wiki
No edit summary
No edit summary
Line 228: Line 228:
                 return "Property '" .. propName .. "' not found in baseClass for unit '" .. unitName .. "'"
                 return "Property '" .. propName .. "' not found in baseClass for unit '" .. unitName .. "'"
             end
             end
        end
    end
    -- Check if property is in format "cost.propertyName" (for accessing unitCost object)
    local costAccess = string.match(property, "^cost%.(.+)$")
    if costAccess then
        local unitCost = unit["unitCost"]
        if unitCost == nil then
            return "unitCost not found for unit '" .. unitName .. "'"
        end
        if type(unitCost) ~= "table" then
            return "unitCost is not an object for unit '" .. unitName .. "'"
        end
        local propName = costAccess
        local value = unitCost[propName]
        if value ~= nil then
            return tostring(value)
        else
            return "Property '" .. propName .. "' not found in unitCost for unit '" .. unitName .. "'"
         end
         end
     end
     end

Revision as of 21:54, 9 December 2025

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}}
Property 'baseName' not found for unit 'Apotheosis'
First Upgrade Name of upgraded unit (localized)
{{#invoke:Unit|get|Angel|upgradeName}}
Property 'upgradeName' not found for unit 'Angel'
Second Upgrade Name of alt. upgraded unit (localized)
{{#invoke:Unit|get|Angel|altUpgradeName}}
Property 'altUpgradeName' not found for unit 'Angel'
Image name Name without spaces and apostrophes, suitable for wiki file names
{{#invoke:Unit|get|Sun's Aegis|imageName}}
Property 'imageName' not found for unit 'Sun's Aegis'
description Narrative description (localized with data substitutions)
{{#invoke:Unit|get|Angel|description}}
Property 'description' not found for unit 'Angel'
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}}
Property 'cost' not found for unit 'Angel'
cost.gold
cost.resource
Access to specific property in unitCost object
{{#invoke:Unit|get|Angel|cost.gold}}
{{#invoke:Unit|get|Angel|cost.gemstones}}
2400
1
upgradeCost Upgrade cost
{{#invoke:Unit|get|Archangel|upgradeCost}}
Property 'upgradeCost' not found for unit 'Archangel'
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}}
Property 'excaptionInTooltip' not found in passives[1] for unit 'Giant Toad'
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 "Array '" .. arrayName .. "' not found for unit '" .. unitName .. "'"
            end

            if type(array) ~= "table" then
                return "Property '" .. arrayName .. "' is not an array for unit '" .. unitName .. "'"
            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 "Property '" .. propName .. "' not found in " .. arrayName .. "[" .. index .. "] for unit '" .. unitName .. "'"
                    end
                end
            else
                return "Array '" .. arrayName .. "' does not contain index " .. index .. " for unit '" .. unitName .. "'"
            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 "baseClass not found for unit '" .. unitName .. "'"
        end

        if type(baseClass) ~= "table" then
            return "baseClass is not an object for unit '" .. unitName .. "'"
        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 "Property '" .. propName .. "' not found in baseClass for unit '" .. unitName .. "'"
            end
        end
    end

    -- Check if property is in format "cost.propertyName" (for accessing unitCost object)
    local costAccess = string.match(property, "^cost%.(.+)$")
    if costAccess then
        local unitCost = unit["unitCost"]
        if unitCost == nil then
            return "unitCost not found for unit '" .. unitName .. "'"
        end

        if type(unitCost) ~= "table" then
            return "unitCost is not an object for unit '" .. unitName .. "'"
        end

        local propName = costAccess
        local value = unitCost[propName]

        if value ~= nil then
            return tostring(value)
        else
            return "Property '" .. propName .. "' not found in unitCost for unit '" .. unitName .. "'"
        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)
        else
            return tostring(unit[property])
        end
    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 "Property '" .. property .. "' not found for unit '" .. unitName .. "'"
end

return Unit