Module:LocUnitData: Difference between revisions
From Heroes of Might and Magic: Olden Era Official Wiki
mNo edit summary |
m Correct typo in unit name (the game uses american 'Armored' rather than british 'Armoured') |
||
| (78 intermediate revisions by 2 users not shown) | |||
| Line 3: | Line 3: | ||
-- Подключаем зависимости | -- Подключаем зависимости | ||
local LangStrings = require("Module:LocGetString") | local LangStrings = require("Module:LocGetString") | ||
local | local UnitModule = require("Module:Unit") -- наш новый модуль | ||
local | ----------------------------------------------- | ||
-- ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ | |||
----------------------------------------------- | |||
local factions = { | |||
Temple = { | |||
"Swordsman", | |||
"Guard Captain", | |||
"Sun's Aegis", | |||
"Crossbowman", | |||
"Austringer", | |||
"Marksman", | |||
"Griffin", | |||
"Temple Griffin", | |||
"Guardian Griffin", | |||
"Lightweaver", | |||
"Hierophant", | |||
"Sun Herald", | |||
"Cavalry", | |||
"Noble Cavalry", | |||
"Sunspear Cavalry", | |||
"Inquisitor", | |||
"Mother Superior", | |||
"Excommunicator", | |||
"Angel", | |||
"Archangel", | |||
"Apotheosis" | |||
}, | |||
Necropolis = { | |||
"Skeleton", | |||
"Skeleton Warrior", | |||
"Skeleton Archer", | |||
"Wight", | |||
"Wraith", | |||
"Phantasm", | |||
"Undead Pet", | |||
"Barghest", | |||
"Armored Hound", | |||
"Graverobber", | |||
"Merchant of Death", | |||
"Kennelmaster", | |||
"Lich", | |||
"Pestilent Lich", | |||
"Sanguine Lich", | |||
"Dread Knight", | |||
"Avatar of War", | |||
"Hollow Reaper", | |||
"Vampire", | |||
"Vampire Lord", | |||
"Vampire Scholar" | |||
}, | |||
Dungeon = { | |||
"Troglodyte", | |||
"Infernal Troglodyte", | |||
"Toxic Troglodyte", | |||
"Infiltrator", | |||
"Guile Infiltrator", | |||
"Bleak Infiltrator", | |||
"Onyx Dancer", | |||
"Jasper Dancer", | |||
"Aureate Dancer", | |||
"Minotaur", | |||
"Minotaur Lord", | |||
"Minotaur Vanguard", | |||
"Medusa", | |||
"Medusa Sculptor", | |||
"Medusa Queen", | |||
"Hydra", | |||
"Chthonic Hydra", | |||
"Infernal Hydra", | |||
"Cave Dragon", | |||
"Black Dragon", | |||
"Ashen Dragon" | |||
}, | |||
Schism = { | |||
"Ra'Shoth", | |||
"Stinging Ra'Shoth", | |||
"Ferocious Ra'Shoth", | |||
"Cultist", | |||
"Binder", | |||
"Votary", | |||
"Aga'Shoth Rider", | |||
"Aga'Shoth Tamer", | |||
"Aga'Shoth Matha", | |||
"Grand Shoth", | |||
"Unspeakable Shoth", | |||
"Unthinkable Shoth", | |||
"Concubus", | |||
"Mistress of Chains", | |||
"Bewitcher", | |||
"Arbitrator", | |||
"Rift Arbitrator", | |||
"Bloated Arbitrator", | |||
"Abyssal Envoy", | |||
"Abyssal Overseer", | |||
"Abyssal Executor" | |||
}, | |||
Sylvan = { | |||
"Faun", | |||
"Faun Archer", | |||
"Faun Warrior", | |||
"Hoplet", | |||
"Dawn Hoplet", | |||
"Dusk Hoplet", | |||
"Vine Iriyad", | |||
"Fungal Iriyad", | |||
"Crystal Iriyad", | |||
"Aqualotl", | |||
"Blooming Aqualotl", | |||
"Polar Aqualotl", | |||
"Herbomancer", | |||
"Sporemancer", | |||
"Murmurmancer", | |||
"Qilin", | |||
"Thunder Qilin", | |||
"Mist Qilin", | |||
"Phoenix", | |||
"Flaming Phoenix", | |||
"Energy Phoenix" | |||
}, | |||
Hive = { | |||
"Gnat", | |||
"Warden Gnat", | |||
"Ravager Gnat", | |||
"Locust", | |||
"Overgrown Locust", | |||
"Harvester Locust", | |||
"Hornet", | |||
"Chanter", | |||
"Stinger_(unit)", | |||
"Mantis", | |||
"Vermilion Mantis", | |||
"Royal Mantis", | |||
"Reaver", | |||
"Menacing Reaver", | |||
"Maniacal Reaver", | |||
"Waurms", | |||
"Devourer", | |||
"Pyroboros", | |||
"Hive Queen", | |||
"Hive Mother", | |||
"Hive Huntress" | |||
}, | |||
Neutral = { | |||
"Pixie", | |||
"Peasant", | |||
"Ghoul", | |||
"Halfling", | |||
"Animated Armour", | |||
"Groll", | |||
"Kittenhorn", | |||
"Starchild", | |||
"Primal Remnant", | |||
"Unicorn", | |||
"Giant Toad", | |||
"Faerie Dragon", | |||
"Couatl", | |||
"Dragonslayer", | |||
"Worldwatcher", | |||
"Sentinel of Glory", | |||
"Obsidian Dragon", | |||
"Lich Dragon", | |||
"Avatar" | |||
} | |||
} | |||
-- Таблица с иконками фракций | |||
local factionIcons = { | |||
Temple = "Human icon.png", | |||
Necropolis = "Undead icon.png", | |||
Dungeon = "Dungeon icon.png", | |||
Schism = "Unfrozen icon.png", | |||
Sylvan = "Spring icon.png", | |||
Hive = "Hive icon.png", | |||
Neutral = "Neutral_faction.png" | |||
} | |||
-- Таблица с названиями фракций | |||
local factionNames = { | |||
Temple = "Temple", | |||
Necropolis = "Necropolis", | |||
Dungeon = "Dungeon", | |||
Schism = "Schism", | |||
Sylvan = "Sylvan", | |||
Hive = "Hive", | |||
Neutral = "Neutral" | |||
} | |||
-- Таблица с путями для фракций | |||
local factionPages = { | |||
Temple = "Temple_Units", | |||
Necropolis = "Necropolis_Units", | |||
Dungeon = "Dungeon_Units", | |||
Schism = "Schism_Units", | |||
Sylvan = "Sylvan_Units", | |||
Hive = "Hive_Units", | |||
Neutral = "Neutral_Units" | |||
} | |||
----------------------------------------------------- | ----------------------------------------------------- | ||
-- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ | -- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ | ||
----------------------------------------------------- | ----------------------------------------------------- | ||
local function | -- Обёртка: вызвать Module:Unit get с нужными аргументами | ||
local function unitGet(name, property, lang) | |||
return UnitModule.get({ args = { name, property, lang or "en" } }) | |||
end | end | ||
| Line 68: | Line 240: | ||
Range = "[[File:Battle icon Onesword.png|24px|alt=Attack Type]]", | Range = "[[File:Battle icon Onesword.png|24px|alt=Attack Type]]", | ||
Growth = "[[File:Skill Recruitment.png|24px|alt=Growth]]", | Growth = "[[File:Skill Recruitment.png|24px|alt=Growth]]", | ||
Movement = "[[File:Icon Stats Moving.png|24px|alt=Attack Type]]" | Movement = "[[File:Icon Stats Moving.png|24px|alt=Attack Type]]", | ||
AbilityRank = getLabel("tooltipRang", lang), | |||
EnergyCost = getLabel("unit_energy", lang), | |||
} | } | ||
end | end | ||
function | |||
if | function p.printUnitData(frame) | ||
local name = frame.args[1] or frame.args.name | |||
if name then | |||
name = name:gsub("'", "’") | |||
name = name:gsub("%_%(unit%)", "") | |||
else | |||
return "Ошибка: не указано имя юнита" | |||
end | |||
local | local lang = frame.args.lang or "en" | ||
local | local success, result = pcall(function() | ||
return p.getUnitData(name, lang) | |||
end) | |||
if not success then | |||
return "Ошибка: " .. tostring(result) | |||
end | |||
local | -- Простой вывод всей таблицы | ||
local output = "=== ДАННЫЕ ДЛЯ ОТЛАДКИ ===\n" | |||
output = output .. "Тип результата: " .. type(result) .. "\n\n" | |||
for | if type(result) == "table" then | ||
output = output .. "Содержимое таблицы:\n" | |||
for key, value in pairs(result) do | |||
output = output .. " " .. tostring(key) .. " = " .. tostring(value) .. "\n" | |||
end | end | ||
else | |||
output = output .. "Результат: " .. tostring(result) | |||
end | end | ||
return | return "<pre>" .. output .. "</pre>" | ||
end | end | ||
| Line 113: | Line 291: | ||
lang = lang or "en" | lang = lang or "en" | ||
-- Проверяем что юнит существует | |||
local | local idCheck = unitGet(name, "id", lang) | ||
if not | if not idCheck or idCheck == "" or idCheck:find("^Unit '") then | ||
return nil, "Unit not found: " .. name | |||
end | |||
-- Базовые поля через Module:Unit | |||
local locName = unitGet(name, "name", lang) | |||
local locDesc = unitGet(name, "description", lang) | |||
local fraction = unitGet(name, "faction", lang) | |||
local tier = unitGet(name, "tier", lang) -- уже римские цифры | |||
local cost = unitGet(name, "cost", lang) | |||
local growth = unitGet(name, "weeklyIncrement", lang) | |||
local expBonus = unitGet(name, "expBonus", lang) | |||
local squadValue = unitGet(name, "squadValue", lang) | |||
local imageName = unitGet(name, "imageName", "en") -- без пробелов/апострофов | |||
local | -- Линейка апгрейдов (имена-ключи на en, для обхода в getUnits) | ||
local | local baseUnitName = unitGet(name, "baseName", "en") | ||
local upgUnitName = unitGet(name, "upgradeName", "en") | |||
local altUnitName = unitGet(name, "altUpgradeName", "en") | |||
-- | -- Статы (числовые поля) | ||
local | local statFields = { | ||
local | "hp", "offence", "defence", "damageMin", "damageMax", | ||
"speed", "initiative", "moral", "luck" | |||
} | |||
local stats = {} | |||
for _, f in ipairs(statFields) do | |||
stats[f] = unitGet(name, f, lang) | |||
end | |||
-- moveType и тип атаки | |||
local moveType = unitGet(name, "moveType", lang) | |||
local attack_type = "" | |||
-- | -- Способности | ||
local abilities = {} | local abilities = {} | ||
-- BaseClass — добавляем первым | |||
local bcName = unitGet(name, "baseClass.name", lang) | |||
local | if bcName and bcName ~= "" then | ||
local | local bcDesc = unitGet(name, "baseClass.description", lang) | ||
local bcIcon = unitGet(name, "baseClass.icon", lang) | |||
table.insert(abilities, { name = bcName, description = bcDesc, icon = bcIcon, isBaseClass = true }) | |||
end | |||
if | -- Активные способности | ||
local i = 1 | |||
while true do | |||
local aname = unitGet(name, "abilities." .. i .. ".name", lang) | |||
if not aname or aname == "" then break end | |||
local adesc = unitGet(name, "abilities." .. i .. ".description", lang) | |||
local aicon = unitGet(name, "abilities." .. i .. ".icon", lang) | |||
local atype = unitGet(name, "abilities." .. i .. ".type", lang) | |||
local ability_tier = unitGet(name, "abilities." .. i .. ".ability_tier", lang) | |||
local energyLevel = unitGet(name, "abilities." .. i .. ".energyLevel", lang) | |||
local excaption = unitGet(name, "abilities." .. i .. ".excaptionInTooltip", lang) | |||
table.insert(abilities, { | |||
name = aname, | |||
description = adesc, | |||
icon = aicon, | |||
atype = atype, | |||
ability_tier = ability_tier, | |||
energyLevel = energyLevel, | |||
excaption = excaption, | |||
}) | |||
if atype == "attack_melee" or atype == "attack_range" or atype == "attack_shoot" then | |||
attack_type = aname | |||
end | end | ||
i = i + 1 | |||
end | |||
-- Пассивки (добавляем к способностям) | |||
i = 1 | |||
while true do | |||
local aname = unitGet(name, "passives." .. i .. ".name", lang) | |||
if not aname or aname == "" then break end | |||
local adesc = unitGet(name, "passives." .. i .. ".description", lang) | |||
local aicon = unitGet(name, "passives." .. i .. ".icon", lang) | |||
table.insert(abilities, { name = aname, description = adesc, icon = aicon }) | |||
i = i + 1 | |||
end | end | ||
-- Изображение: Faction_ImageName_Battlefield.png | |||
local factionRaw = unitGet(name, "faction", "en") -- внутренний ключ (human, nature, ...) | |||
local factionCap = factionRaw ~= "" and (factionRaw:sub(1,1):upper() .. factionRaw:sub(2)) or "" | |||
local image = factionCap .. "_" .. imageName .. "_Battlefield.png" | |||
return { | return { | ||
id = | id = idCheck, | ||
name = name, | upgradeLine = { baseUnitName, upgUnitName, altUnitName }, | ||
locName = locName, | name = name, | ||
locDesc = locDesc, | locName = locName, | ||
image = image | locDesc = locDesc, | ||
image = image, | |||
tier = | tier = tier, | ||
fraction = fraction, | fraction = fraction, | ||
attack_type = | attack_type = attack_type, | ||
stats = stats, | stats = stats, | ||
cost = cost, | cost = cost, | ||
growth = | growth = growth, | ||
moveType = | moveType = moveType, | ||
expBonus = | expBonus = expBonus, | ||
squadValue = | squadValue = squadValue, | ||
abilities = abilities | abilities = abilities, | ||
} | } | ||
end | end | ||
| Line 173: | Line 403: | ||
-- РИСОВАНИЕ ТАБЛИЦЫ | -- РИСОВАНИЕ ТАБЛИЦЫ | ||
----------------------------------------------------- | ----------------------------------------------------- | ||
function p.renderUnitTable(unit, lang) | function p.renderUnitTable(unit, lang, baseUnit) | ||
if not unit then return "Invalid unit data" end | if not unit then return "Invalid unit data" end | ||
lang = lang or "en" | lang = lang or "en" | ||
| Line 181: | Line 411: | ||
if x == nil or x == "" then return "—" end | if x == nil or x == "" then return "—" end | ||
return tostring(x) | return tostring(x) | ||
end | |||
local function diffStat(stat) | |||
if not baseUnit then return val(unit.stats[stat]) end | |||
local newVal = tonumber(unit.stats[stat]) | |||
local oldVal = tonumber(baseUnit.stats[stat]) | |||
if not newVal or not oldVal then return val(unit.stats[stat]) end | |||
if newVal > oldVal then | |||
return string.format("%s <span style='color:green;font-weight:bold;'>↑</span>", val(newVal)) | |||
elseif newVal < oldVal then | |||
return string.format("%s <span style='color:red;font-weight:bold;'>↓</span>", val(newVal)) | |||
else | |||
return val(newVal) | |||
end | |||
end | end | ||
| Line 186: | Line 432: | ||
local cost = unit.cost or {} | local cost = unit.cost or {} | ||
text = " | text = "<div style='max-width:1000px;margin:0 auto;'>\n" | ||
text = text .. " | -- заголовок юнита | ||
text = text .. "<div style='text-align:left;line-height:1.4;margin-bottom:8px;'>" | |||
text = text .. "<div style='font-size:1.5em;font-weight:bold;'>" .. unit.locName .. "</div>" | |||
text = text .. "<div style='font-size:0.9em;color:gray;'>" .. val(unit.locDesc) .. "</div>" | |||
text = text .. "</div>\n" | |||
-- | -- основной блок: таблица статов + картинка | ||
text = text .. " | text = text .. "<div style='display:flex;flex-wrap:wrap;justify-content:center;gap:10px;'>\n" | ||
text = text .. "|-\n|" .. "[[File:Health Icon.png|24px|link=Combat#Health|alt=Health]] ".. L.Health .. " || " .. | -- левая часть: таблица статов | ||
text = text .. "|-\n|" .. "[[File:Icon Stats Attack.png|24px|link=Combat#Attack|alt=Attack]] ".. L.Attack .. " || " .. | text = text .. "<div style='flex:1 1 400px;min-width:280px;'>\n" | ||
text = text .. "|-\n|" .. "[[File:Icon Stats Defence.png|24px|link=Combat#Defence|alt=Defense]] ".. L.Defense .. " || " .. | text = text .. "{| class='wikitable' style='width:100%;text-align:left;margin:0;'\n" | ||
text = text .. "|-\n|" .. "[[File:Icon Stats Damage.png|24px|link=Combat#Damage|alt=Damage]] ".. L.Damage .. " || " .. | text = text .. "|-\n|" .. "[[File:Health Icon.png|24px|link=Combat#Health|alt=Health]] ".. L.Health .. " || " .. diffStat("hp") .. " || " .. L.Tier .. " || " .. val(unit.tier) .. "\n" | ||
text = text .. "|-\n|" .. "[[File:Icon Stats Speed.png|24px|link=Combat#Speed|alt=Speed]] ".. L.Speed .. " || " .. | text = text .. "|-\n|" .. "[[File:Icon Stats Attack.png|24px|link=Combat#Attack|alt=Attack]] ".. L.Attack .. " || " .. diffStat("offence") .. " || " .. L.Fraction .. " || " .. val(unit.fraction) .. "\n" | ||
text = text .. "|-\n|" .. "[[File:Icon Stats Initiative.png|24px|link=Combat#Initiative|alt=Initiative]] ".. L.Initiative .. " || " .. | text = text .. "|-\n|" .. "[[File:Icon Stats Defence.png|24px|link=Combat#Defence|alt=Defense]] ".. L.Defense .. " || " .. diffStat("defence") .. " || " .. L.Range .. " || " .. val(unit.attack_type) .. "\n" | ||
text = text .. "|-\n|" .. "[[File:Icon Stats Morale.png|24px|link=Combat#Morale|alt=Morale]] ".. L.Morale .. " || " .. | text = text .. "|-\n|" .. "[[File:Icon Stats Damage.png|24px|link=Combat#Damage|alt=Damage]] ".. L.Damage .. " || " .. diffStat("damageMin") .. "–" .. diffStat("damageMax") .. " || " .. L.Movement .. " || " .. val(unit.moveType) .. "\n" | ||
text = text .. "|-\n|" .. "[[File:Icon Stats Luck.png|24px|link=Combat#Luck|alt=Luck]] ".. L.Luck .. " || " .. | text = text .. "|-\n|" .. "[[File:Icon Stats Speed.png|24px|link=Combat#Speed|alt=Speed]] ".. L.Speed .. " || " .. diffStat("speed") .. " || " .. L.Price .. " || " .. cost .. "\n" | ||
text = text .. "|-\n|" .. "[[File:Icon Stats Initiative.png|24px|link=Combat#Initiative|alt=Initiative]] ".. L.Initiative .. " || " .. diffStat("initiative") .. " || " .. L.Growth .. " || " .. val(unit.growth) .. "\n" | |||
text = text .. "|-\n|" .. "[[File:Icon Stats Morale.png|24px|link=Combat#Morale|alt=Morale]] ".. L.Morale .. " || " .. diffStat("moral") .. " || " .. L.Experience .. " || " .. val(unit.expBonus) .. "\n" | |||
text = text .. "|-\n|" .. "[[File:Icon Stats Luck.png|24px|link=Combat#Luck|alt=Luck]] ".. L.Luck .. " || " .. diffStat("luck") .. " || " .. L.SquadValue .. " || " .. val(unit.squadValue) .. "\n" | |||
text = text .. "|}\n" | text = text .. "|}\n" | ||
text = text .. "</div>\n" | |||
-- правая часть: картинка | -- правая часть: картинка | ||
text = text .. " | text = text .. "<div style='flex:1 1 300px;min-width:260px;text-align:center;'>[[File:" .. (unit.image or "Temple_Swordsman_Battlefield.png") .. "|400px]]</div>\n" | ||
text = text .. "</div>\n" -- закрытие блока flex | |||
-- способности | -- способности | ||
text = text .. "<div style='margin-top:10px;'>\n" | |||
if unit.abilities and #unit.abilities > 0 then | if unit.abilities and #unit.abilities > 0 then | ||
text = text .. "{| class='wikitable' style='width:100%;text-align:left;margin:0;'\n" | |||
text = text .. "{| class='wikitable' style='width:100%;text-align:left;'\n" | |||
for _, ab in ipairs(unit.abilities) do | for _, ab in ipairs(unit.abilities) do | ||
local icon = "[[File:" .. (ab.icon ~= "" and ab.icon or "icon bug.png") .. "|60px]]" | local icon = "[[File:" .. (ab.icon ~= "" and ab.icon or "icon bug.png") .. "|60px]]" | ||
-- Строка с именем и описанием | |||
local body = "<b>" .. ab.name .. "</b>" | |||
-- Для активных способностей — ранг и стоимость | |||
if not ab.isBaseClass then | |||
local meta = {} | |||
if ab.energyLevel and ab.energyLevel ~= "" then | |||
local costTpl = LangStrings.getText({ args = { sid = "tooltipsAbilityCost", lang = lang } }) or "Ability cost: {0}" | |||
local costStr = costTpl:gsub("{0}", ab.energyLevel) | |||
table.insert(meta, costStr) | |||
end | |||
if #meta > 0 then | |||
body = body .. " <span style='color:gray;font-size:0.9em;'>| " .. table.concat(meta, " | ") .. "</span>" | |||
end | |||
end | |||
body = body .. "<br>" .. (ab.description or "") | |||
-- Ограничения (excaptionInTooltip) | |||
if ab.excaption and ab.excaption ~= "" then | |||
body = body .. "<br><span style='color:#888;font-size:0.9em;font-style:italic;'>" .. ab.excaption .. "</span>" | |||
end | |||
text = text .. "|-\n| style='text-align:center;width:80px;vertical-align:middle;' | " .. icon .. | text = text .. "|-\n| style='text-align:center;width:80px;vertical-align:middle;' | " .. icon .. | ||
" || style='padding:10px;' | | " || style='padding:10px;' | " .. body .. "\n" | ||
end | end | ||
text = text .. "|}\n" | text = text .. "|}\n" | ||
else | else | ||
text = text .. " | text = text .. "—\n" | ||
end | end | ||
text = text .. "</div>\n" | |||
text = text .. " | text = text .. "</div>\n" | ||
return text | return text | ||
| Line 233: | Line 511: | ||
function p.getUnit(frame) | function p.getUnit(frame) | ||
local name = frame.args[1] or frame.args.name | local name = frame.args[1] or frame.args.name | ||
name = name and name:gsub("'", "’") or name | |||
name = name:gsub("%_%(unit%)", "") -- for Stinger unit and Stinger hero | |||
local lang = frame.args.lang or "en" | local lang = frame.args.lang or "en" | ||
local unit, err = p.getUnitData(name, lang) | local unit, err = p.getUnitData(name, lang) | ||
if not unit then return err end | if not unit then return err end | ||
return p.renderUnitTable(unit, lang) | return p.renderUnitTable(unit, lang) | ||
end | |||
function p.getUnits(frame) | |||
local name = frame.args[1] or frame.args.name | |||
local lang = frame.args.lang or "en" | |||
local unit, err = p.getUnitData(name, lang) | |||
if not unit then | |||
return err | |||
end | |||
-- основной юнит | |||
local text = p.renderUnitTable(unit, lang) | |||
-- связанные из линейки | |||
for _, unitName in ipairs(unit.upgradeLine) do | |||
if unitName and unitName ~= name then | |||
local relatedUnit = p.getUnitData(unitName, lang) | |||
text = text .. "\n\n" .. p.renderUnitTable(relatedUnit, lang, unit) | |||
end | |||
end | |||
return text | |||
end | |||
-- TABS for units | |||
function p.unitTabs(frame) | |||
local lang = frame.args.lang or "en" | |||
local tabberContent = {} | |||
for i = 1, math.huge do | |||
local unit = frame.args[i] | |||
if not unit then break end | |||
unit = mw.text.trim(unit) | |||
local title = LangStrings.translate{ args = { text = unit, lang = lang } } | |||
table.insert(tabberContent, "|-| " .. title .. " =") | |||
table.insert(tabberContent, "<br>") | |||
table.insert(tabberContent, "{{#invoke:LocUnitData|getUnits|" .. unit .. "|lang=" .. lang .. "}}") | |||
end | |||
if #tabberContent == 0 then return "" end | |||
local fullTabber = "<tabber>\n" .. table.concat(tabberContent, "\n") .. "\n</tabber>" | |||
-- Обрабатываем всю разметку tabber как викитекст | |||
return frame:preprocess(fullTabber) | |||
end | |||
-- NavBox for fractions | |||
function p.navBox(frame) | |||
local faction = frame.args.faction | |||
local lang = frame.args.lang or "en" | |||
local langSuffix = lang ~= "en" and "/" .. lang or "" | |||
local result = '{| class="wikitable mw-collapsible white-toggle-button" style="width:100%;"\n' | |||
result = result .. '!colspan="8"|[[Units' .. langSuffix .. '|Units]]\n' | |||
-- Функция для получения перевода названия фракции | |||
local function getFactionName(fac) | |||
if fac == "Neutral" then | |||
return "Neutral" -- нейтралы остаются на английском | |||
end | |||
local sidMap = { | |||
Temple = "world_cheat_dropdown_fraction_human", | |||
Necropolis = "world_cheat_dropdown_fraction_undead", | |||
Dungeon = "world_cheat_dropdown_fraction_dungeon", | |||
Sylvan = "world_cheat_dropdown_fraction_nature", | |||
Hive = "world_cheat_dropdown_fraction_demon", | |||
Schism = "world_cheat_dropdown_fraction_unfrozen" | |||
} | |||
local sid = sidMap[fac] | |||
if sid then | |||
return LangStrings.getText({ args = { sid = sid, lang = lang } }) or fac | |||
end | |||
return fac | |||
end | |||
-- Функция для создания ссылки на юнита с flex выравниванием | |||
local function getUnitLink(unitName) | |||
local locName = unitGet(unitName, "name", lang) | |||
return '<div style="display:flex; align-items:center; margin-bottom:4px;">[[File:' .. unitName:gsub(" ", "_") .. '_icon.png|28px|link=' .. unitName .. langSuffix .. ']] [[' .. unitName .. langSuffix .. '|' .. locName .. ']]</div>' | |||
end | |||
-- Функция для рендеринга одной фракции | |||
local function renderFaction(fac) | |||
local factionName = getFactionName(fac) | |||
local row = '|-\n' | |||
row = row .. '|align="center" style="vertical-align:middle;"|[[File:' .. factionIcons[fac] .. '|50px|link=' .. factionPages[fac] .. langSuffix .. '|' .. factionName .. ' Units]]<br>[[' .. factionPages[fac] .. langSuffix .. '|' .. factionName .. ']]\n' | |||
local units = factions[fac] | |||
if fac == "Neutral" then | |||
-- Особый формат для нейтральных юнитов | |||
row = row .. '|colspan="7" style="vertical-align:middle;"|' | |||
for i, unit in ipairs(units) do | |||
local locName = unitGet(unit, "name", lang) | |||
row = row .. '<div style="display:inline-flex; align-items:center; margin:0 8px 4px 0;">[[File:' .. unit:gsub(" ", "_") .. '_icon.png|28px|link=' .. unit .. langSuffix .. ']] [[' .. unit .. langSuffix .. '|' .. locName .. ']]</div>' | |||
if i < #units then | |||
row = row .. ' • ' | |||
end | |||
end | |||
else | |||
-- Обычные фракции (7 колонок по 3 юнита) | |||
row = row .. '| style="vertical-align:middle;"|' | |||
for i = 1, 21, 3 do | |||
if i > 1 then | |||
row = row .. '\n| style="vertical-align:middle;"|' | |||
end | |||
for j = 0, 2 do | |||
if units[i + j] then | |||
row = row .. getUnitLink(units[i + j]) | |||
end | |||
end | |||
end | |||
end | |||
return row .. '\n' | |||
end | |||
-- Основная логика отображения | |||
if faction and factions[faction] then | |||
result = result .. renderFaction(faction) | |||
else | |||
local allFactions = {"Temple", "Necropolis", "Dungeon", "Schism", "Sylvan", "Hive", "Neutral"} | |||
for _, fac in ipairs(allFactions) do | |||
result = result .. renderFaction(fac) | |||
end | |||
end | |||
return result .. '|}\n' | |||
end | end | ||
return p | return p | ||
Latest revision as of 11:55, 10 July 2026
Documentation for this module may be created at Module:LocUnitData/doc
local p = {}
-- Подключаем зависимости
local LangStrings = require("Module:LocGetString")
local UnitModule = require("Module:Unit") -- наш новый модуль
-----------------------------------------------
-- ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ
-----------------------------------------------
local factions = {
Temple = {
"Swordsman",
"Guard Captain",
"Sun's Aegis",
"Crossbowman",
"Austringer",
"Marksman",
"Griffin",
"Temple Griffin",
"Guardian Griffin",
"Lightweaver",
"Hierophant",
"Sun Herald",
"Cavalry",
"Noble Cavalry",
"Sunspear Cavalry",
"Inquisitor",
"Mother Superior",
"Excommunicator",
"Angel",
"Archangel",
"Apotheosis"
},
Necropolis = {
"Skeleton",
"Skeleton Warrior",
"Skeleton Archer",
"Wight",
"Wraith",
"Phantasm",
"Undead Pet",
"Barghest",
"Armored Hound",
"Graverobber",
"Merchant of Death",
"Kennelmaster",
"Lich",
"Pestilent Lich",
"Sanguine Lich",
"Dread Knight",
"Avatar of War",
"Hollow Reaper",
"Vampire",
"Vampire Lord",
"Vampire Scholar"
},
Dungeon = {
"Troglodyte",
"Infernal Troglodyte",
"Toxic Troglodyte",
"Infiltrator",
"Guile Infiltrator",
"Bleak Infiltrator",
"Onyx Dancer",
"Jasper Dancer",
"Aureate Dancer",
"Minotaur",
"Minotaur Lord",
"Minotaur Vanguard",
"Medusa",
"Medusa Sculptor",
"Medusa Queen",
"Hydra",
"Chthonic Hydra",
"Infernal Hydra",
"Cave Dragon",
"Black Dragon",
"Ashen Dragon"
},
Schism = {
"Ra'Shoth",
"Stinging Ra'Shoth",
"Ferocious Ra'Shoth",
"Cultist",
"Binder",
"Votary",
"Aga'Shoth Rider",
"Aga'Shoth Tamer",
"Aga'Shoth Matha",
"Grand Shoth",
"Unspeakable Shoth",
"Unthinkable Shoth",
"Concubus",
"Mistress of Chains",
"Bewitcher",
"Arbitrator",
"Rift Arbitrator",
"Bloated Arbitrator",
"Abyssal Envoy",
"Abyssal Overseer",
"Abyssal Executor"
},
Sylvan = {
"Faun",
"Faun Archer",
"Faun Warrior",
"Hoplet",
"Dawn Hoplet",
"Dusk Hoplet",
"Vine Iriyad",
"Fungal Iriyad",
"Crystal Iriyad",
"Aqualotl",
"Blooming Aqualotl",
"Polar Aqualotl",
"Herbomancer",
"Sporemancer",
"Murmurmancer",
"Qilin",
"Thunder Qilin",
"Mist Qilin",
"Phoenix",
"Flaming Phoenix",
"Energy Phoenix"
},
Hive = {
"Gnat",
"Warden Gnat",
"Ravager Gnat",
"Locust",
"Overgrown Locust",
"Harvester Locust",
"Hornet",
"Chanter",
"Stinger_(unit)",
"Mantis",
"Vermilion Mantis",
"Royal Mantis",
"Reaver",
"Menacing Reaver",
"Maniacal Reaver",
"Waurms",
"Devourer",
"Pyroboros",
"Hive Queen",
"Hive Mother",
"Hive Huntress"
},
Neutral = {
"Pixie",
"Peasant",
"Ghoul",
"Halfling",
"Animated Armour",
"Groll",
"Kittenhorn",
"Starchild",
"Primal Remnant",
"Unicorn",
"Giant Toad",
"Faerie Dragon",
"Couatl",
"Dragonslayer",
"Worldwatcher",
"Sentinel of Glory",
"Obsidian Dragon",
"Lich Dragon",
"Avatar"
}
}
-- Таблица с иконками фракций
local factionIcons = {
Temple = "Human icon.png",
Necropolis = "Undead icon.png",
Dungeon = "Dungeon icon.png",
Schism = "Unfrozen icon.png",
Sylvan = "Spring icon.png",
Hive = "Hive icon.png",
Neutral = "Neutral_faction.png"
}
-- Таблица с названиями фракций
local factionNames = {
Temple = "Temple",
Necropolis = "Necropolis",
Dungeon = "Dungeon",
Schism = "Schism",
Sylvan = "Sylvan",
Hive = "Hive",
Neutral = "Neutral"
}
-- Таблица с путями для фракций
local factionPages = {
Temple = "Temple_Units",
Necropolis = "Necropolis_Units",
Dungeon = "Dungeon_Units",
Schism = "Schism_Units",
Sylvan = "Sylvan_Units",
Hive = "Hive_Units",
Neutral = "Neutral_Units"
}
-----------------------------------------------------
-- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
-----------------------------------------------------
-- Обёртка: вызвать Module:Unit get с нужными аргументами
local function unitGet(name, property, lang)
return UnitModule.get({ args = { name, property, lang or "en" } })
end
local function getLabel(sid, lang)
local text = LangStrings.getText({ args = { sid = sid, lang = lang } })
return text or "—"
end
local function getUnitLabels(lang)
return {
Attack = getLabel("unit_attack", lang),
Defense = getLabel("unit_defence", lang),
Damage = getLabel("unit_damage", lang),
Health = getLabel("unit_health", lang),
Speed = getLabel("unit_speed", lang),
Initiative = getLabel("unit_init", lang),
Morale = getLabel("unit_moral", lang),
Luck = getLabel("unit_luck", lang),
Tier = getLabel("tooltipRang", lang),
Fraction = getLabel("faction_laws", lang),
Price = "[[File:Skill Economy.png|24px|alt=Price]]",
Experience = getLabel("hero_ui_exp", lang),
SquadValue = "SquadValue",
Range = "[[File:Battle icon Onesword.png|24px|alt=Attack Type]]",
Growth = "[[File:Skill Recruitment.png|24px|alt=Growth]]",
Movement = "[[File:Icon Stats Moving.png|24px|alt=Attack Type]]",
AbilityRank = getLabel("tooltipRang", lang),
EnergyCost = getLabel("unit_energy", lang),
}
end
function p.printUnitData(frame)
local name = frame.args[1] or frame.args.name
if name then
name = name:gsub("'", "’")
name = name:gsub("%_%(unit%)", "")
else
return "Ошибка: не указано имя юнита"
end
local lang = frame.args.lang or "en"
local success, result = pcall(function()
return p.getUnitData(name, lang)
end)
if not success then
return "Ошибка: " .. tostring(result)
end
-- Простой вывод всей таблицы
local output = "=== ДАННЫЕ ДЛЯ ОТЛАДКИ ===\n"
output = output .. "Тип результата: " .. type(result) .. "\n\n"
if type(result) == "table" then
output = output .. "Содержимое таблицы:\n"
for key, value in pairs(result) do
output = output .. " " .. tostring(key) .. " = " .. tostring(value) .. "\n"
end
else
output = output .. "Результат: " .. tostring(result)
end
return "<pre>" .. output .. "</pre>"
end
-----------------------------------------------------
-- ОСНОВНАЯ ЛОГИКА: получение данных юнита
-----------------------------------------------------
function p.getUnitData(name, lang)
if not name then return nil, "No unit name provided" end
lang = lang or "en"
-- Проверяем что юнит существует
local idCheck = unitGet(name, "id", lang)
if not idCheck or idCheck == "" or idCheck:find("^Unit '") then
return nil, "Unit not found: " .. name
end
-- Базовые поля через Module:Unit
local locName = unitGet(name, "name", lang)
local locDesc = unitGet(name, "description", lang)
local fraction = unitGet(name, "faction", lang)
local tier = unitGet(name, "tier", lang) -- уже римские цифры
local cost = unitGet(name, "cost", lang)
local growth = unitGet(name, "weeklyIncrement", lang)
local expBonus = unitGet(name, "expBonus", lang)
local squadValue = unitGet(name, "squadValue", lang)
local imageName = unitGet(name, "imageName", "en") -- без пробелов/апострофов
-- Линейка апгрейдов (имена-ключи на en, для обхода в getUnits)
local baseUnitName = unitGet(name, "baseName", "en")
local upgUnitName = unitGet(name, "upgradeName", "en")
local altUnitName = unitGet(name, "altUpgradeName", "en")
-- Статы (числовые поля)
local statFields = {
"hp", "offence", "defence", "damageMin", "damageMax",
"speed", "initiative", "moral", "luck"
}
local stats = {}
for _, f in ipairs(statFields) do
stats[f] = unitGet(name, f, lang)
end
-- moveType и тип атаки
local moveType = unitGet(name, "moveType", lang)
local attack_type = ""
-- Способности
local abilities = {}
-- BaseClass — добавляем первым
local bcName = unitGet(name, "baseClass.name", lang)
if bcName and bcName ~= "" then
local bcDesc = unitGet(name, "baseClass.description", lang)
local bcIcon = unitGet(name, "baseClass.icon", lang)
table.insert(abilities, { name = bcName, description = bcDesc, icon = bcIcon, isBaseClass = true })
end
-- Активные способности
local i = 1
while true do
local aname = unitGet(name, "abilities." .. i .. ".name", lang)
if not aname or aname == "" then break end
local adesc = unitGet(name, "abilities." .. i .. ".description", lang)
local aicon = unitGet(name, "abilities." .. i .. ".icon", lang)
local atype = unitGet(name, "abilities." .. i .. ".type", lang)
local ability_tier = unitGet(name, "abilities." .. i .. ".ability_tier", lang)
local energyLevel = unitGet(name, "abilities." .. i .. ".energyLevel", lang)
local excaption = unitGet(name, "abilities." .. i .. ".excaptionInTooltip", lang)
table.insert(abilities, {
name = aname,
description = adesc,
icon = aicon,
atype = atype,
ability_tier = ability_tier,
energyLevel = energyLevel,
excaption = excaption,
})
if atype == "attack_melee" or atype == "attack_range" or atype == "attack_shoot" then
attack_type = aname
end
i = i + 1
end
-- Пассивки (добавляем к способностям)
i = 1
while true do
local aname = unitGet(name, "passives." .. i .. ".name", lang)
if not aname or aname == "" then break end
local adesc = unitGet(name, "passives." .. i .. ".description", lang)
local aicon = unitGet(name, "passives." .. i .. ".icon", lang)
table.insert(abilities, { name = aname, description = adesc, icon = aicon })
i = i + 1
end
-- Изображение: Faction_ImageName_Battlefield.png
local factionRaw = unitGet(name, "faction", "en") -- внутренний ключ (human, nature, ...)
local factionCap = factionRaw ~= "" and (factionRaw:sub(1,1):upper() .. factionRaw:sub(2)) or ""
local image = factionCap .. "_" .. imageName .. "_Battlefield.png"
return {
id = idCheck,
upgradeLine = { baseUnitName, upgUnitName, altUnitName },
name = name,
locName = locName,
locDesc = locDesc,
image = image,
tier = tier,
fraction = fraction,
attack_type = attack_type,
stats = stats,
cost = cost,
growth = growth,
moveType = moveType,
expBonus = expBonus,
squadValue = squadValue,
abilities = abilities,
}
end
-----------------------------------------------------
-- РИСОВАНИЕ ТАБЛИЦЫ
-----------------------------------------------------
function p.renderUnitTable(unit, lang, baseUnit)
if not unit then return "Invalid unit data" end
lang = lang or "en"
local L = getUnitLabels(lang)
local function val(x)
if x == nil or x == "" then return "—" end
return tostring(x)
end
local function diffStat(stat)
if not baseUnit then return val(unit.stats[stat]) end
local newVal = tonumber(unit.stats[stat])
local oldVal = tonumber(baseUnit.stats[stat])
if not newVal or not oldVal then return val(unit.stats[stat]) end
if newVal > oldVal then
return string.format("%s <span style='color:green;font-weight:bold;'>↑</span>", val(newVal))
elseif newVal < oldVal then
return string.format("%s <span style='color:red;font-weight:bold;'>↓</span>", val(newVal))
else
return val(newVal)
end
end
local stats = unit.stats or {}
local cost = unit.cost or {}
text = "<div style='max-width:1000px;margin:0 auto;'>\n"
-- заголовок юнита
text = text .. "<div style='text-align:left;line-height:1.4;margin-bottom:8px;'>"
text = text .. "<div style='font-size:1.5em;font-weight:bold;'>" .. unit.locName .. "</div>"
text = text .. "<div style='font-size:0.9em;color:gray;'>" .. val(unit.locDesc) .. "</div>"
text = text .. "</div>\n"
-- основной блок: таблица статов + картинка
text = text .. "<div style='display:flex;flex-wrap:wrap;justify-content:center;gap:10px;'>\n"
-- левая часть: таблица статов
text = text .. "<div style='flex:1 1 400px;min-width:280px;'>\n"
text = text .. "{| class='wikitable' style='width:100%;text-align:left;margin:0;'\n"
text = text .. "|-\n|" .. "[[File:Health Icon.png|24px|link=Combat#Health|alt=Health]] ".. L.Health .. " || " .. diffStat("hp") .. " || " .. L.Tier .. " || " .. val(unit.tier) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Attack.png|24px|link=Combat#Attack|alt=Attack]] ".. L.Attack .. " || " .. diffStat("offence") .. " || " .. L.Fraction .. " || " .. val(unit.fraction) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Defence.png|24px|link=Combat#Defence|alt=Defense]] ".. L.Defense .. " || " .. diffStat("defence") .. " || " .. L.Range .. " || " .. val(unit.attack_type) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Damage.png|24px|link=Combat#Damage|alt=Damage]] ".. L.Damage .. " || " .. diffStat("damageMin") .. "–" .. diffStat("damageMax") .. " || " .. L.Movement .. " || " .. val(unit.moveType) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Speed.png|24px|link=Combat#Speed|alt=Speed]] ".. L.Speed .. " || " .. diffStat("speed") .. " || " .. L.Price .. " || " .. cost .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Initiative.png|24px|link=Combat#Initiative|alt=Initiative]] ".. L.Initiative .. " || " .. diffStat("initiative") .. " || " .. L.Growth .. " || " .. val(unit.growth) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Morale.png|24px|link=Combat#Morale|alt=Morale]] ".. L.Morale .. " || " .. diffStat("moral") .. " || " .. L.Experience .. " || " .. val(unit.expBonus) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Luck.png|24px|link=Combat#Luck|alt=Luck]] ".. L.Luck .. " || " .. diffStat("luck") .. " || " .. L.SquadValue .. " || " .. val(unit.squadValue) .. "\n"
text = text .. "|}\n"
text = text .. "</div>\n"
-- правая часть: картинка
text = text .. "<div style='flex:1 1 300px;min-width:260px;text-align:center;'>[[File:" .. (unit.image or "Temple_Swordsman_Battlefield.png") .. "|400px]]</div>\n"
text = text .. "</div>\n" -- закрытие блока flex
-- способности
text = text .. "<div style='margin-top:10px;'>\n"
if unit.abilities and #unit.abilities > 0 then
text = text .. "{| class='wikitable' style='width:100%;text-align:left;margin:0;'\n"
for _, ab in ipairs(unit.abilities) do
local icon = "[[File:" .. (ab.icon ~= "" and ab.icon or "icon bug.png") .. "|60px]]"
-- Строка с именем и описанием
local body = "<b>" .. ab.name .. "</b>"
-- Для активных способностей — ранг и стоимость
if not ab.isBaseClass then
local meta = {}
if ab.energyLevel and ab.energyLevel ~= "" then
local costTpl = LangStrings.getText({ args = { sid = "tooltipsAbilityCost", lang = lang } }) or "Ability cost: {0}"
local costStr = costTpl:gsub("{0}", ab.energyLevel)
table.insert(meta, costStr)
end
if #meta > 0 then
body = body .. " <span style='color:gray;font-size:0.9em;'>| " .. table.concat(meta, " | ") .. "</span>"
end
end
body = body .. "<br>" .. (ab.description or "")
-- Ограничения (excaptionInTooltip)
if ab.excaption and ab.excaption ~= "" then
body = body .. "<br><span style='color:#888;font-size:0.9em;font-style:italic;'>" .. ab.excaption .. "</span>"
end
text = text .. "|-\n| style='text-align:center;width:80px;vertical-align:middle;' | " .. icon ..
" || style='padding:10px;' | " .. body .. "\n"
end
text = text .. "|}\n"
else
text = text .. "—\n"
end
text = text .. "</div>\n"
text = text .. "</div>\n"
return text
end
-----------------------------------------------------
-- Главная точка входа для шаблона
-----------------------------------------------------
function p.getUnit(frame)
local name = frame.args[1] or frame.args.name
name = name and name:gsub("'", "’") or name
name = name:gsub("%_%(unit%)", "") -- for Stinger unit and Stinger hero
local lang = frame.args.lang or "en"
local unit, err = p.getUnitData(name, lang)
if not unit then return err end
return p.renderUnitTable(unit, lang)
end
function p.getUnits(frame)
local name = frame.args[1] or frame.args.name
local lang = frame.args.lang or "en"
local unit, err = p.getUnitData(name, lang)
if not unit then
return err
end
-- основной юнит
local text = p.renderUnitTable(unit, lang)
-- связанные из линейки
for _, unitName in ipairs(unit.upgradeLine) do
if unitName and unitName ~= name then
local relatedUnit = p.getUnitData(unitName, lang)
text = text .. "\n\n" .. p.renderUnitTable(relatedUnit, lang, unit)
end
end
return text
end
-- TABS for units
function p.unitTabs(frame)
local lang = frame.args.lang or "en"
local tabberContent = {}
for i = 1, math.huge do
local unit = frame.args[i]
if not unit then break end
unit = mw.text.trim(unit)
local title = LangStrings.translate{ args = { text = unit, lang = lang } }
table.insert(tabberContent, "|-| " .. title .. " =")
table.insert(tabberContent, "<br>")
table.insert(tabberContent, "{{#invoke:LocUnitData|getUnits|" .. unit .. "|lang=" .. lang .. "}}")
end
if #tabberContent == 0 then return "" end
local fullTabber = "<tabber>\n" .. table.concat(tabberContent, "\n") .. "\n</tabber>"
-- Обрабатываем всю разметку tabber как викитекст
return frame:preprocess(fullTabber)
end
-- NavBox for fractions
function p.navBox(frame)
local faction = frame.args.faction
local lang = frame.args.lang or "en"
local langSuffix = lang ~= "en" and "/" .. lang or ""
local result = '{| class="wikitable mw-collapsible white-toggle-button" style="width:100%;"\n'
result = result .. '!colspan="8"|[[Units' .. langSuffix .. '|Units]]\n'
-- Функция для получения перевода названия фракции
local function getFactionName(fac)
if fac == "Neutral" then
return "Neutral" -- нейтралы остаются на английском
end
local sidMap = {
Temple = "world_cheat_dropdown_fraction_human",
Necropolis = "world_cheat_dropdown_fraction_undead",
Dungeon = "world_cheat_dropdown_fraction_dungeon",
Sylvan = "world_cheat_dropdown_fraction_nature",
Hive = "world_cheat_dropdown_fraction_demon",
Schism = "world_cheat_dropdown_fraction_unfrozen"
}
local sid = sidMap[fac]
if sid then
return LangStrings.getText({ args = { sid = sid, lang = lang } }) or fac
end
return fac
end
-- Функция для создания ссылки на юнита с flex выравниванием
local function getUnitLink(unitName)
local locName = unitGet(unitName, "name", lang)
return '<div style="display:flex; align-items:center; margin-bottom:4px;">[[File:' .. unitName:gsub(" ", "_") .. '_icon.png|28px|link=' .. unitName .. langSuffix .. ']] [[' .. unitName .. langSuffix .. '|' .. locName .. ']]</div>'
end
-- Функция для рендеринга одной фракции
local function renderFaction(fac)
local factionName = getFactionName(fac)
local row = '|-\n'
row = row .. '|align="center" style="vertical-align:middle;"|[[File:' .. factionIcons[fac] .. '|50px|link=' .. factionPages[fac] .. langSuffix .. '|' .. factionName .. ' Units]]<br>[[' .. factionPages[fac] .. langSuffix .. '|' .. factionName .. ']]\n'
local units = factions[fac]
if fac == "Neutral" then
-- Особый формат для нейтральных юнитов
row = row .. '|colspan="7" style="vertical-align:middle;"|'
for i, unit in ipairs(units) do
local locName = unitGet(unit, "name", lang)
row = row .. '<div style="display:inline-flex; align-items:center; margin:0 8px 4px 0;">[[File:' .. unit:gsub(" ", "_") .. '_icon.png|28px|link=' .. unit .. langSuffix .. ']] [[' .. unit .. langSuffix .. '|' .. locName .. ']]</div>'
if i < #units then
row = row .. ' • '
end
end
else
-- Обычные фракции (7 колонок по 3 юнита)
row = row .. '| style="vertical-align:middle;"|'
for i = 1, 21, 3 do
if i > 1 then
row = row .. '\n| style="vertical-align:middle;"|'
end
for j = 0, 2 do
if units[i + j] then
row = row .. getUnitLink(units[i + j])
end
end
end
end
return row .. '\n'
end
-- Основная логика отображения
if faction and factions[faction] then
result = result .. renderFaction(faction)
else
local allFactions = {"Temple", "Necropolis", "Dungeon", "Schism", "Sylvan", "Hive", "Neutral"}
for _, fac in ipairs(allFactions) do
result = result .. renderFaction(fac)
end
end
return result .. '|}\n'
end
return p


