Module:Unit: Difference between revisions
From Heroes of Might and Magic: Olden Era Official Wiki
No edit summary |
No edit summary |
||
| Line 166: | Line 166: | ||
local unit = units[unitName] | local unit = units[unitName] | ||
if not unit then return "" end | if not unit then return "" end | ||
local displayName = getUnitName(unit, lang) | |||
-- Remove apostrophes, replace spaces with nothing | |||
displayName = displayName:gsub("[’]", "'") | |||
return displayName | |||
end | end | ||
| Line 268: | Line 271: | ||
local displayName = getUnitName(unit, lang) | local displayName = getUnitName(unit, lang) | ||
-- Remove apostrophes, replace spaces with nothing | -- Remove apostrophes, replace spaces with nothing | ||
displayName = displayName:gsub("[ | displayName = displayName:gsub("[’]", "'") | ||
return displayName | return displayName | ||
end | end | ||
Revision as of 23:55, 4 May 2026
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}}
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
-- Cache for reverse lookup: id -> unitName
local idIndexCache = nil
-- Load localization module
local LangStrings = require("Module:LocGetString")
-- Roman numeral conversion (1–20 should cover all tiers)
local function toRoman(n)
n = tonumber(n)
if not n or n <= 0 then return tostring(n or "") end
local vals = {1000,900,500,400,100,90,50,40,10,9,5,4,1}
local syms = {"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"}
local result = ""
for i, v in ipairs(vals) do
while n >= v do
result = result .. syms[i]
n = n - v
end
end
return result
end
-- Fallback display names used when localization returns empty.
-- Для neutral локализации нет — задаём вручную для всех языков.
local FACTION_DISPLAY = {
demon = "Hive",
dungeon = "Dungeon",
human = "Temple",
nature = "Grove",
undead = "Necropolis",
unfrozen = "Schism",
}
local NEUTRAL_BY_LANG = {
en = "Neutral",
ru = "Нейтральные",
pl = "Neutralne",
}
-- 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 = {}
idIndexCache = {}
local units = loadJson("Data:units.json")
for name, unit in pairs(units) do
dataCache[name] = unit
-- Build reverse index: id -> display name (key in the JSON object)
if unit.id and unit.id ~= "" then
idIndexCache[unit.id] = name
end
end
return dataCache
end
-- Return the reverse-index cache (loads units first if needed)
local function getIdIndex()
if not idIndexCache then loadUnits() end
return idIndexCache
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
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 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 type(data[i]) == "table" then
part = part:gsub("{(%d+)}", function(n)
local idx = tonumber(n) + 1
local val = data[i][idx]
return val ~= nil and tostring(val) or "{" .. n .. "}"
end)
replacements[i] = part
else
part = part:gsub("{0}", tostring(data[i]))
replacements[i] = part
end
end
elseif data[i] ~= nil then
replacements[i] = tostring(data[i])
end
end
for i = #item.data_sid + 1, #data do
if data[i] ~= nil then
replacements[i] = tostring(data[i])
end
end
else
for i, val in ipairs(data) do
replacements[i] = tostring(val)
end
end
text = text:gsub("{(%d+)}", function(n)
local idx = tonumber(n) + 1
local val = replacements[idx]
return val or "{" .. n .. "}"
end)
return text
end
-- Return the display name of a unit (with localization)
local function getUnitName(unit, lang)
if unit.sid_name and unit.sid_name ~= "" then
return getLocalizedText(unit.sid_name, lang, false)
end
local id = unit.id or ""
if id ~= "" then
return getLocalizedText(id .. "_name", lang, false)
end
return unit.name or "No name"
end
-- Return the display name of an upgrade/base unit by its id string.
-- Returns "" if the id is nil, empty, or no matching unit exists.
local function getNameById(id, lang)
if not id or id == "" then return "" end
local idx = getIdIndex()
local unitName = idx[id]
if not unitName then return "" end
local units = loadUnits()
local unit = units[unitName]
if not unit then return "" end
local displayName = getUnitName(unit, lang)
-- Remove apostrophes, replace spaces with nothing
displayName = displayName:gsub("[’]", "'")
return displayName
end
-- Helper: get unitCost property
local function getUnitCostProperty(unitCost, propName)
if unitCost == nil or type(unitCost) ~= "table" then return "" end
local value = unitCost[propName]
return value ~= nil and tostring(value) or ""
end
-- Helper: format a cost table as wiki markup
local function formatCost(unitCost)
if unitCost == nil or type(unitCost) ~= "table" then return "" end
local result = {}
if unitCost.gold and type(unitCost.gold) == "number" and unitCost.gold > 0 then
table.insert(result, tostring(unitCost.gold) ..
"[[File:Icon Resource Gold.png|32px|link=Resources#Gold|Gold]]")
end
for resName, resValue in pairs(unitCost) do
if resName ~= "gold" and type(resValue) == "number" and resValue > 0 then
local resNameUpper = string.upper(string.sub(resName, 1, 1)) .. string.sub(resName, 2)
table.insert(result, tostring(resValue) ..
"[[File:Icon Resource " .. resNameUpper .. ".png|32px|link=Resources#" ..
resNameUpper .. "|" .. resNameUpper .. "]]")
end
end
return table.concat(result, " ")
end
-- Helper: process a sub-object (array element or baseClass) for a property request
local function getSubObjectProperty(element, propName, lang)
if element == nil then return "" end
if type(element) ~= "table" then return "" end
-- 'name' – prefer sid_name
if propName == "name" then
if element["sid_name"] then
return getLocalizedText(element["sid_name"], lang, false)
elseif element["name"] then
return tostring(element["name"])
else
return ""
end
end
-- 'description' – use sid_desc with data substitution
if propName == "description" then
if element["sid_desc"] then
return getLocalizedDescription(element, lang)
else
return ""
end
end
local value = element[propName]
if value ~= nil then
-- Localized fields that should return empty when the sid is missing
if propName == "abilityType" or propName == "excaptionInTooltip" or propName == "infoDescription" then
return getLocalizedText(value, lang, true)
else
return tostring(value)
end
end
return ""
end
-- ============================================================
-- Main exported function
-- ============================================================
function Unit.get(frame)
local args = frame.args
local unitName = mw.text.trim(args[1] or args.unit or "")
local property = mw.text.trim(args[2] or args.property or "")
local lang = mw.text.trim(args[3] or args.lang or "en")
if unitName == "" then return "Unit name not specified" end
if property == "" then return "Property not specified" end
local units = loadUnits()
local unit = units[unitName]
if not unit then return "Unit '" .. unitName .. "' not found" end
-- --------------------------------------------------------
-- tier → Roman numerals
-- --------------------------------------------------------
if property == "tier" then
local t = unit["tier"]
if t == nil and unit.stats then t = unit.stats["tier"] end
return t ~= nil and toRoman(t) or ""
end
-- --------------------------------------------------------
-- name
-- --------------------------------------------------------
if property == "name" then
local displayName = getUnitName(unit, lang)
-- Remove apostrophes, replace spaces with nothing
displayName = displayName:gsub("[’]", "'")
return displayName
end
-- --------------------------------------------------------
-- faction → localized faction name
-- --------------------------------------------------------
if property == "faction" then
local factionKey = unit["faction"]
if not factionKey or factionKey == "" then return "" end
-- neutral отсутствует в локализации — возвращаем хардкод по языку
if factionKey == "neutral" then
return NEUTRAL_BY_LANG[lang] or NEUTRAL_BY_LANG["en"]
end
local loc = getLocalizedText("world_cheat_dropdown_fraction_" .. factionKey, lang, true)
if loc and loc ~= "" then return loc end
return FACTION_DISPLAY[factionKey] or factionKey
end
-- --------------------------------------------------------
-- Вспомогательная: получить базовый id цепочки.
-- phoenix_upg_alt -> phoenix
-- phoenix_upg -> phoenix
-- phoenix -> phoenix
-- --------------------------------------------------------
local function getBaseId()
local myId = unit["id"] or ""
-- Сначала пробуем убрать длинный суффикс, потом короткий
local base = myId:match("^(.+)_upg_alt$") or myId:match("^(.+)_upg$") or myId
return base
end
-- --------------------------------------------------------
-- baseName -> имя базового существа цепочки (всегда одно и то же
-- для базы, грейда и альтгрейда)
-- --------------------------------------------------------
if property == "baseName" then
return getNameById(getBaseId(), lang)
end
-- --------------------------------------------------------
-- upgradeName -> имя первого апгрейда (baseId_upg)
-- одинаково для любого юнита цепочки
-- --------------------------------------------------------
if property == "upgradeName" then
return getNameById(getBaseId() .. "_upg", lang)
end
-- --------------------------------------------------------
-- altUpgradeName -> имя альт-апгрейда (baseId_upg_alt)
-- одинаково для любого юнита цепочки
-- --------------------------------------------------------
if property == "altUpgradeName" then
return getNameById(getBaseId() .. "_upg_alt", lang)
end
-- --------------------------------------------------------
-- upgradeCost / upgradeCost.propertyName
-- Разница в стоимости между апгрейднутым существом и базовым.
-- Одинакова для обоих апгрейдов (alt и обычного), т.к. оба
-- стоят одинаково, а базовый юнит — тот, чей upgradeSid == id текущего.
-- Для базового существа (у которого нет предшественника) — пустая строка.
-- --------------------------------------------------------
-- Найти базовый юнит через стрипинг суффикса id (та же логика что getBaseId)
local function findBaseUnit()
local myId = unit["id"] or ""
if myId == "" then return nil end
local baseId = myId:match("^(.+)_upg_alt$") or myId:match("^(.+)_upg$")
if not baseId then return nil end -- уже базовый, нет предшественника
local idx = getIdIndex()
local baseName = idx[baseId]
if not baseName then return nil end
return units[baseName]
end
-- Вычислить разницу unitCost: текущий минус базовый
-- Возвращает таблицу {resource -> delta} только с положительными значениями
local function calcUpgradeCostDiff()
local baseUnit = findBaseUnit()
if not baseUnit then return nil end
local myCost = unit["unitCost"] or {}
local baseCost = baseUnit["unitCost"] or {}
local diff = {}
local allKeys = {}
for k in pairs(myCost) do allKeys[k] = true end
for k in pairs(baseCost) do allKeys[k] = true end
for k in pairs(allKeys) do
local delta = (myCost[k] or 0) - (baseCost[k] or 0)
if delta > 0 then
diff[k] = delta
end
end
return diff
end
if property == "upgradeCost" then
local diff = calcUpgradeCostDiff()
if not diff then return "" end
return formatCost(diff)
end
local upgradeCostAccess = string.match(property, "^upgradeCost%.(.+)$")
if upgradeCostAccess then
local diff = calcUpgradeCostDiff()
if not diff then return "" end
local value = diff[upgradeCostAccess]
return value ~= nil and tostring(value) or ""
end
-- --------------------------------------------------------
-- imageName → unit display name without spaces and apostrophes,
-- suitable for wiki file names
-- --------------------------------------------------------
if property == "imageName" then
local displayName = getUnitName(unit, lang)
-- Remove apostrophes, replace spaces with nothing
displayName = displayName:gsub("['’ ]", "")
return displayName
end
-- --------------------------------------------------------
-- cost → formatted hire cost
-- --------------------------------------------------------
if property == "cost" then
return formatCost(unit["unitCost"])
end
-- cost.propertyName / unitCost.propertyName
local costAccess = string.match(property, "^cost%.(.+)$")
if costAccess then return getUnitCostProperty(unit["unitCost"], costAccess) end
local unitCostAccess = string.match(property, "^unitCost%.(.+)$")
if unitCostAccess then return getUnitCostProperty(unit["unitCost"], unitCostAccess) end
-- --------------------------------------------------------
-- arrayName.index.propertyName (e.g. abilities.1.name)
-- --------------------------------------------------------
local arrayName, indexStr, propName = string.match(property, "^(.+)%.(%d+)%.(.+)$")
if arrayName and indexStr and propName then
local index = tonumber(indexStr)
local array = unit[arrayName]
if array == nil or type(array) ~= "table" then return "" end
local element = array[index]
if element == nil then return "" end
return getSubObjectProperty(element, propName, lang)
end
-- --------------------------------------------------------
-- baseClass.propertyName
-- --------------------------------------------------------
local baseClassAccess = string.match(property, "^baseClass%.(.+)$")
if baseClassAccess then
local baseClass = unit["baseClass"]
if baseClass == nil or type(baseClass) ~= "table" then return "" end
return getSubObjectProperty(baseClass, baseClassAccess, lang)
end
-- --------------------------------------------------------
-- Direct property on unit object
-- --------------------------------------------------------
if unit[property] ~= nil then
if property == "name" and unit["sid_name"] then
return getLocalizedText(unit["sid_name"], lang, false)
elseif property == "description" and unit["sid_desc"] then
return getLocalizedDescription(unit, lang)
else
return tostring(unit[property])
end
end
-- description via id
if property == "description" then
local id = unit.id or ""
if id ~= "" then
return getLocalizedText(id .. "_narrativeDescription", lang, true)
end
return ""
end
-- Fallback: check stats sub-table
if unit.stats and unit.stats[property] ~= nil then
return tostring(unit.stats[property])
end
return ""
end
return Unit


