Module:LocUnitData: Difference between revisions

From Heroes of Might and Magic: Olden Era Official Wiki
No edit summary
No edit summary
Line 566: Line 566:
     result = result .. '!colspan="8"|[[Units' .. (lang ~= "en" and "/" .. lang or "") .. '|Units]]\n'
     result = result .. '!colspan="8"|[[Units' .. (lang ~= "en" and "/" .. lang or "") .. '|Units]]\n'
      
      
     if faction and factions[faction] then
     -- Функция для рендеринга одной фракции
        -- Показываем только одну фракцию
    local function renderFaction(fac)
         result = result .. '|-\n'
         local row = '|-\n'
         result = result .. '|align="center"|[[File:' .. factionIcons[faction] .. '|50px|link=' .. factionPages[faction] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. factionNames[faction] .. ' Units]]<br>[[' .. factionPages[faction] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. factionNames[faction] .. ']]\n'
         row = row .. '|align="center"|[[File:' .. factionIcons[fac] .. '|50px|link=' .. factionPages[fac] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. factionNames[fac] .. ' Units]]<br>[[' .. factionPages[fac] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. factionNames[fac] .. ']]\n'
          
          
         local units = factions[faction]
         local units = factions[fac]
         if faction == "Neutral" then
         if fac == "Neutral" then
             -- Особый формат для нейтральных юнитов
             -- Особый формат для нейтральных юнитов
             result = result .. '|colspan="7"|'
             row = row .. '|colspan="7"|'
             for i, unit in ipairs(units) do
             for i, unit in ipairs(units) do
                 result = result .. '[[File:' .. unit:gsub(" ", "_") .. '_icon.png|28px|link=' .. unit .. (lang ~= "en" and "/" .. lang or "") .. ']] [[' .. unit .. (lang ~= "en" and "/" .. lang or "") .. '|' .. unit .. ']]'
                 row = row .. '[[File:' .. unit:gsub(" ", "_") .. '_icon.png|28px|link=' .. unit .. (lang ~= "en" and "/" .. lang or "") .. ']] [[' .. unit .. (lang ~= "en" and "/" .. lang or "") .. '|' .. unit .. ']]'
                 if i < #units then
                 if i < #units then
                     result = result .. ' • '
                     row = row .. ' • '
                 end
                 end
             end
             end
         else
         else
             -- Обычные фракции (7 колонок по 3 юнита)
             -- Обычные фракции (7 колонок по 3 юнита)
             -- ЯВНО начинаем первую ячейку с юнитами
             row = row .. '| '
            result = result .. '| '
             for i = 1, 21, 3 do
             for i = 1, 21, 3 do
                 if i > 1 then
                 if i > 1 then
                     result = result .. '\n| '
                     row = row .. '\n| '
                 end
                 end
                 for j = 0, 2 do
                 for j = 0, 2 do
                     if units[i + j] then
                     if units[i + j] then
                        -- ДОБАВЛЕНО: перевод названий юнитов для одиночной фракции
                         local locName = LocGameEntities.getLocName({ args = { name = units[i + j], lang = lang } })
                         local locName = LocGameEntities.getLocName({ args = { name = units[i + j], lang = lang } })
                         result = result .. '[[File:' .. units[i + j]:gsub(" ", "_") .. '_icon.png|28px|link=' .. units[i + j] .. (lang ~= "en" and "/" .. lang or "") .. ']] [[' .. units[i + j] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. locName .. ']]'
                         row = row .. '[[File:' .. units[i + j]:gsub(" ", "_") .. '_icon.png|28px|link=' .. units[i + j] .. (lang ~= "en" and "/" .. lang or "") .. ']] [[' .. units[i + j] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. locName .. ']]'
                         if j < 2 and units[i + j + 1] then
                         if j < 2 and units[i + j + 1] then
                             result = result .. ' <br> '
                             row = row .. ' <br> '
                         end
                         end
                     end
                     end
Line 601: Line 599:
             end
             end
         end
         end
         -- УБРАНО: лишний перенос строки в конце для одиночной фракции
        return row
    end
   
    if faction and factions[faction] then
         -- Показываем только одну фракцию
        result = result .. renderFaction(faction)
     else
     else
         -- Показываем все фракции (оригинальный вид)
         -- Показываем все фракции
         local allFactions = {"Temple", "Necropolis", "Dungeon", "Schism", "Sylvan", "Hive", "Neutral"}
         local allFactions = {"Temple", "Necropolis", "Dungeon", "Schism", "Sylvan", "Hive", "Neutral"}
       
         for _, fac in ipairs(allFactions) do
         for _, fac in ipairs(allFactions) do
             result = result .. '|-\n'  -- НАЧАЛО НОВОЙ СТРОКИ ДЛЯ КАЖДОЙ ФРАКЦИИ
             result = result .. renderFaction(fac)
            result = result .. '|align="center"|[[File:' .. factionIcons[fac] .. '|50px|link=' .. factionPages[fac] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. factionNames[fac] .. ' Units]]<br>[[' .. factionPages[fac] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. factionNames[fac] .. ']]\n'
           
            local units = factions[fac]
            if fac == "Neutral" then
                result = result .. '|colspan="7"|'
                for i, unit in ipairs(units) do
                    result = result .. '[[File:' .. unit:gsub(" ", "_") .. '_icon.png|28px|link=' .. unit .. (lang ~= "en" and "/" .. lang or "") .. ']] [[' .. unit .. (lang ~= "en" and "/" .. lang or "") .. '|' .. unit .. ']]'
                    if i < #units then
                        result = result .. ' • '
                    end
                end
            else
                -- 7 колонок по 3 юнита
                -- ЯВНО начинаем первую ячейку с юнитами
                result = result .. '| '
                for i = 1, 21, 3 do
                    if i > 1 then
                        result = result .. '\n| '
                    end
                    for j = 0, 2 do
                        if units[i + j] then
                            local locName = LocGameEntities.getLocName({ args = { name = units[i + j], lang = lang } })
                            result = result .. '[[File:' .. units[i + j]:gsub(" ", "_") .. '_icon.png|28px|link=' .. units[i + j] .. (lang ~= "en" and "/" .. lang or "") .. ']] [[' .. units[i + j] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. locName .. ']]'
                            if j < 2 and units[i + j + 1] then
                                result = result .. ' <br> '
                            end
                        end
                    end
                end
            end
            result = result .. '\n'  -- Оставляем перенос для всех фракций
         end
         end
     end
     end

Revision as of 12:30, 19 November 2025

Documentation for this module may be created at Module:LocUnitData/doc

local p = {}

-- Подключаем зависимости
local LangStrings = require("Module:LocGetString")
local LocGameEntities = require("Module:LocGameEntities")

local dataCache = nil


-----------------------------------------------
-- ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ
-----------------------------------------------
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",
        "Armoured 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",
        "Cthonic 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",
        "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 = "Primordial_Chaos.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 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

local function loadUnits()
    if dataCache then return dataCache end
    dataCache = {}

    local stats = loadJson("Data:UnitsStats.json")
    local data = loadJson("Data:UnitsData.json")

    -- Индексируем stats по id
    local statsById = {}
    for key, item in pairs(stats) do
        local id = item.id or key
        statsById[id] = item
    end

    -- Объединяем
    for name, item in pairs(data) do
        if item.id and statsById[item.id] then
            local merged = {}
            for k, v in pairs(statsById[item.id]) do merged[k] = v end
            for k, v in pairs(item) do merged[k] = v end
            dataCache[name] = merged
        end
    end

    return dataCache
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]]"
    }
end

local function getLocFraction(code, lang)
    local valid = {
        human = true, demon = true, dungeon = true,
        nature = true, undead = true, unfrozen = true
    }
    if not valid[code] then return "-" end
    local sid = "world_cheat_dropdown_fraction_" .. code
    return LangStrings.getText({ args = { sid = sid, lang = lang } })
end


function formatCost(cost)
    if not cost then return "" end
    
    local resourceOrder = {"gold", "gemstones", "mercury", "crystals"}
    
    local resources = {
        gold = {icon = "Icon Resource Gold.png", link = "Resources#Gold", name = "Gold"},
        gemstones = {icon = "Icon Resource GemStone.png", link = "Resources#Gems", name = "Gems"},
        mercury = {icon = "Icon Resource Mercury.png", link = "Resources#Mercury", name = "Mercury"},
        crystals = {icon = "Icon Resource Crystal.png", link = "Resources#crystals", name = "Crystals"}
    }
    
    local parts = {}
    
    for _, resource in ipairs(resourceOrder) do
        if cost[resource] then
            table.insert(parts, cost[resource] .. " [[File:" .. resources[resource].icon .. "|24px|link=" .. resources[resource].link .. "|" .. resources[resource].name .. "]]")
        end
    end
    
    return table.concat(parts, " ")
end


function getUnitNamebyId(id)
    local units = loadUnits()
    for name, unit in pairs(units) do
        if unit.id == id then
            return name
        end
    end
    return nil
end

-- Возвращает:
-- baseUnitName, mainUpgName, altUpgName
function resolveUpgrades(unit)
    if not unit or not unit.id then
        return nil, nil, nil
    end

    local id = unit.id

    -- определяем корневой id
    local rootId = id
        :gsub("_upg_alt$", "")
        :gsub("_upg$", "")

    -- id апгрейдов
    local mainUpgId = rootId .. "_upg"
    local altUpgId  = rootId .. "_upg_alt"

    -- имена
    local baseUnitName = getUnitNamebyId(rootId)
    local mainUpgName  = getUnitNamebyId(mainUpgId)
    local altUpgName   = getUnitNamebyId(altUpgId)

    return baseUnitName, mainUpgName, altUpgName
end

-----------------------------------------------------
-- ОСНОВНАЯ ЛОГИКА: получение данных юнита
-----------------------------------------------------
function p.getUnitData(name, lang)
    if not name then return nil, "No unit name provided" end
    lang = lang or "en"

    local units = loadUnits()
    local unit = units[name]
    if not unit then return nil, "Unit not found: " .. name end

    local stats = unit.stats or {}
    local cost = formatCost(unit.unitCost) or ""

    -- локализованные данные
    local locName = LocGameEntities.getLocName({ args = { name = name, lang = lang } })
    local locDesc = LocGameEntities.getLocDescription({ args = { name = name, lang = lang } })
    local image =  LocGameEntities.getImage({ args = { name = name, lang = lang } })
    local icon =  LocGameEntities.getIcon({ args = { name = name, lang = lang } })
    local fraction = getLocFraction(unit.fraction, lang)

    

    -- способности
    local abilities = {}
    if stats.moveType then
       local moveType = "movetype_" .. stats.moveType
       table.insert(unit.abilities, 1, moveType)
       unit.moveType = LocGameEntities.getLocName({ args = { name = moveType, lang = lang } }) 
    end  

    for _, aid in ipairs(unit.abilities or {}) do
        local aname = LocGameEntities.getLocName({ args = { name = aid, lang = lang } })
        local adesc = LocGameEntities.getLocDescription({ args = { name = aid, lang = lang } })
        local aicon = LocGameEntities.getIcon({ args = { name = aid, lang = lang } })
        if aname and aname ~= "" then
            table.insert(abilities, { id = aid, name = aname, description = adesc, icon = aicon })

            if aid == "attack_melee" or aid == "attack_range" or aid == "attack_shoot" then
                unit.attack_type = aname
            end
        end
    end

    local baseUnit, upgUnit, upgAltUnit = resolveUpgrades(unit)

    return {
        id = unit.id,
        upgradeLine = { baseUnit, upgUnit, upgAltUnit },
        name = name,
        locName = locName,
        locDesc = locDesc,
        image = image,
        icon = icon,
        tier = unit.tier,
        fraction = fraction,
        attack_type = unit.attack_type,
        stats = stats,
        cost = cost,
        growth = unit.weeklyIncrement,
        moveType = unit.moveType,
        expBonus = unit.expBonus,
        squadValue = unit.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]]"
            text = text .. "|-\n| style='text-align:center;width:80px;vertical-align:middle;' | " .. icon ..
                        " || style='padding:10px;' | <b>" .. ab.name .. "</b> — " .. ab.description .. "\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
    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 result = '{| class="wikitable mw-collapsible white-toggle-button" style="width:100%;"\n'
    result = result .. '!colspan="8"|[[Units' .. (lang ~= "en" and "/" .. lang or "") .. '|Units]]\n'
    
    -- Функция для рендеринга одной фракции
    local function renderFaction(fac)
        local row = '|-\n'
        row = row .. '|align="center"|[[File:' .. factionIcons[fac] .. '|50px|link=' .. factionPages[fac] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. factionNames[fac] .. ' Units]]<br>[[' .. factionPages[fac] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. factionNames[fac] .. ']]\n'
        
        local units = factions[fac]
        if fac == "Neutral" then
            -- Особый формат для нейтральных юнитов
            row = row .. '|colspan="7"|'
            for i, unit in ipairs(units) do
                row = row .. '[[File:' .. unit:gsub(" ", "_") .. '_icon.png|28px|link=' .. unit .. (lang ~= "en" and "/" .. lang or "") .. ']] [[' .. unit .. (lang ~= "en" and "/" .. lang or "") .. '|' .. unit .. ']]'
                if i < #units then
                    row = row .. ' • '
                end
            end
        else
            -- Обычные фракции (7 колонок по 3 юнита)
            row = row .. '| '
            for i = 1, 21, 3 do
                if i > 1 then
                    row = row .. '\n| '
                end
                for j = 0, 2 do
                    if units[i + j] then
                        local locName = LocGameEntities.getLocName({ args = { name = units[i + j], lang = lang } })
                        row = row .. '[[File:' .. units[i + j]:gsub(" ", "_") .. '_icon.png|28px|link=' .. units[i + j] .. (lang ~= "en" and "/" .. lang or "") .. ']] [[' .. units[i + j] .. (lang ~= "en" and "/" .. lang or "") .. '|' .. locName .. ']]'
                        if j < 2 and units[i + j + 1] then
                            row = row .. ' <br> '
                        end
                    end
                end
            end
        end
        return row
    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
    
    result = result .. '|}\n'
    
    return result
end

return p