Mudanças entre as edições de "Módulo:Info.Skills"

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
Linha 11: Linha 11:
local nz = utils.nz
local nz = utils.nz
local parseFlags = utils.parseFlags
local parseFlags = utils.parseFlags
local SKILL_META = {}
local function norm(val)
    val = trim(val or "")
    return val ~= "" and val or nil
end
local function normExcept(val, placeholder)
    val = trim(val or "")
    if val == "" then
        return nil
    end
    if placeholder and mw.ustring.upper(val) == placeholder then
        return nil
    end
    return val
end
local function normIcon(val)
    val = trim(val or "")
    if val == "" then
        return nil
    end
    if mw.ustring.lower(val) == "nada.png" then
        return nil
    end
    return val
end
local function currentPageKey()
    local title = mw.title.getCurrentTitle()
    if title and title.fullText and trim(title.fullText) ~= "" then
        return title.fullText
    end
    return "__page"
end
local function ensureSkillMeta(char)
    local key = char
    if not key or key == "" then
        key = currentPageKey()
    end
    if not SKILL_META[key] then
        SKILL_META[key] = {
            byIndex = {},
            byName = {}
        }
    end
    return SKILL_META[key]
end
local function storeSkillMeta(char, index, name, meta)
    meta = meta or {}
    local bucket = ensureSkillMeta(char)
    if not bucket then
        return
    end
    if index then
        bucket.byIndex[index] = meta
    end
    if name and name ~= "" then
        bucket.byName[name] = meta
    end
    -- também guarda por página para páginas especiais (ex.: Usuário:Gurren)
    local pageBucket = ensureSkillMeta(currentPageKey())
    if pageBucket ~= bucket then
        if index then
            pageBucket.byIndex[index] = meta
        end
        if name and name ~= "" then
            pageBucket.byName[name] = meta
        end
    end
end
local function fetchSkillMeta(char, index, name)
    local candidates = {}
    if char and char ~= "" and SKILL_META[char] then
        table.insert(candidates, SKILL_META[char])
    end
    local pageKey = currentPageKey()
    if SKILL_META[pageKey] then
        table.insert(candidates, SKILL_META[pageKey])
    end
    for _, bucket in ipairs(candidates) do
        if index and bucket.byIndex[index] then
            return bucket.byIndex[index]
        end
        if name and name ~= "" and bucket.byName[name] then
            return bucket.byName[name]
        end
    end
    return nil
end
local function hydrateSubAttrsFromMeta(char, sub)
    if not sub then
        return sub
    end
    local ref = trim(sub.refM or sub.m or sub.M or "")
    local idx = tonumber(ref)
    local inherited = fetchSkillMeta(char, idx, sub.name or sub.n)
    if not inherited then
        return sub
    end
    sub.icon = normIcon(sub.icon) or inherited.icon or "Nada.png"
    sub.level = normExcept(sub.level, "NIVEL") or inherited.level or sub.level
    sub.video = norm(sub.video) or inherited.video or sub.video
    sub.energy = sub.energy or inherited.energy
    sub.powerpve = sub.powerpve or inherited.powerpve
    sub.powerpvp = sub.powerpvp or inherited.powerpvp
    sub.cooldown = sub.cooldown or inherited.cooldown
    if ref ~= "" then
        sub.refM = ref
    end
    return sub
end


-- ===== Herança automática de descrições =====
-- ===== Herança automática de descrições =====


-- Busca descrição em skills principais
local function findDescInSkills(data, skillName)
local function findDescInSkills(data, skillName)
     if not data or not data.skills then return nil end
     if not data or not data.skills then return nil end
Linha 142: Linha 23:
end
end


-- Busca descrição em qualquer subskills do módulo
local function findDescInAnySubskills(data, skillName)
local function findDescInAnySubskills(data, skillName)
     if not data or not data.skills then return nil end
     if not data or not data.skills then return nil end
Linha 156: Linha 36:
end
end


-- Busca descrição com herança automática
local function resolveDescWithInheritance(data, skillName, directDesc)
local function resolveDescWithInheritance(data, skillName, directDesc)
    -- 1. Se tem desc direta, usa ela
     if directDesc and type(directDesc) == "table" and next(directDesc) then
     if directDesc and type(directDesc) == "table" and next(directDesc) then
         return directDesc
         return directDesc
     end
     end
    -- 2. Busca em skills principais
     local fromSkills = findDescInSkills(data, skillName)
     local fromSkills = findDescInSkills(data, skillName)
     if fromSkills then
     if fromSkills then
         return fromSkills
         return fromSkills
     end
     end
    -- 3. Busca em outras subskills
     local fromSubskills = findDescInAnySubskills(data, skillName)
     local fromSubskills = findDescInAnySubskills(data, skillName)
     if fromSubskills then
     if fromSubskills then
         return fromSubskills
         return fromSubskills
     end
     end
     return nil
     return nil
end
end


-- ===== Weapon (gera JSON com atributos da arma para uso em {{Skill}}) =====
-- ===== Weapon =====


function p.weapon(frame)
function p.weapon(frame)
Linha 198: Linha 71:
     local a = safeArgs(frame)
     local a = safeArgs(frame)
     local lang = trim((a.lang or (frame:getParent() and frame:getParent().args.lang) or "pt"):lower())
     local lang = trim((a.lang or (frame:getParent() and frame:getParent().args.lang) or "pt"):lower())
    -- Usa resolveCharFromFrames que busca recursivamente em todos os parent frames
     local char = resolveCharFromFrames(frame, a)
     local char = resolveCharFromFrames(frame, a)
     local data = requireCharacterModule(char) or {}
     local data = requireCharacterModule(char) or {}
Linha 223: Linha 95:


     local chosen = desc_i18n[lang] or desc_i18n["pt"] or ""
     local chosen = desc_i18n[lang] or desc_i18n["pt"] or ""
    -- Parse flags CSV
     local flagsArr = parseFlags(a.flags)
     local flagsArr = parseFlags(a.flags)


Linha 240: Linha 110:
         obj.flags = flagsArr
         obj.flags = flagsArr
     end
     end
     if chosen ~= "" then
     if chosen ~= "" then
         obj.desc = chosen
         obj.desc = chosen
Linha 252: Linha 121:
     end
     end


    do
     -- Subskills vindas via |subs=
        local metaIndex = tonumber(a.M)
        local storedName = name or key or ""
        storeSkillMeta(char, metaIndex, storedName ~= "" and storedName or nil, {
            icon = normIcon(a.icon),
            level = normExcept(a.level, "NIVEL"),
            energy = norm(a.energy),
            powerpve = norm(a.powerpve),
            powerpvp = norm(a.powerpvp),
            cooldown = norm(a.cooldown),
            video = norm(a.video)
        })
    end
 
     -- empacota subskills vindas via |subs= (cada {{Subskill}} vira um objeto JSON)
     do
     do
         local subsPacked = a.subs or ""
         local subsPacked = a.subs or ""
Linha 273: Linha 128:
             local ok, sk = pcall(mw.text.jsonDecode, chunk)
             local ok, sk = pcall(mw.text.jsonDecode, chunk)
             if ok and type(sk) == "table" then
             if ok and type(sk) == "table" then
                 table.insert(subsArr, hydrateSubAttrsFromMeta(char, sk))
                 table.insert(subsArr, sk)
             end
             end
         end
         end
         -- se não vier via |subs=, tenta obter do módulo (suborder/subskills)
         -- Fallback: subskills do módulo .lua
         if #subsArr == 0 and a.M and tonumber(a.M) then
         if #subsArr == 0 and a.M and tonumber(a.M) then
             local m = tonumber(a.M)
             local m = tonumber(a.M)
Linha 288: Linha 143:
                     local sub = subskills[n]
                     local sub = subskills[n]
                     if type(sub) == "table" then
                     if type(sub) == "table" then
                        -- HERANÇA: se sub não tem desc, busca automaticamente
                         local resolvedDesc = resolveDescWithInheritance(data, n, sub.desc)
                         local resolvedDesc = resolveDescWithInheritance(data, n, sub.desc)
 
                         local subObj = {
                         local subObj = hydrateSubAttrsFromMeta(char, {
                             n = n,
                             n = n,
                             name = n,
                             name = n,
Linha 306: Linha 159:
                             descEs = resolvedDesc and resolvedDesc.es or nil,
                             descEs = resolvedDesc and resolvedDesc.es or nil,
                             descPl = resolvedDesc and resolvedDesc.pl or nil
                             descPl = resolvedDesc and resolvedDesc.pl or nil
                         })
                         }
                        -- nested subs do módulo (sub.suborder/sub.subskills)
                         if type(sub.suborder) == "table" and type(sub.subskills) == "table" then
                         if type(sub.suborder) == "table" and type(sub.subskills) == "table" then
                             local nested = {}
                             local nested = {}
Linha 313: Linha 165:
                                 local s2 = sub.subskills[nn]
                                 local s2 = sub.subskills[nn]
                                 if type(s2) == "table" then
                                 if type(s2) == "table" then
                                    -- HERANÇA para nested também
                                     local nestedDesc = resolveDescWithInheritance(data, nn, s2.desc)
                                     local nestedDesc = resolveDescWithInheritance(data, nn, s2.desc)
 
                                     table.insert(nested, {
                                     table.insert(nested, hydrateSubAttrsFromMeta(char, {
                                         n = nn,
                                         n = nn,
                                         name = nn,
                                         name = nn,
Linha 331: Linha 181:
                                         descEs = nestedDesc and nestedDesc.es or nil,
                                         descEs = nestedDesc and nestedDesc.es or nil,
                                         descPl = nestedDesc and nestedDesc.pl or nil
                                         descPl = nestedDesc and nestedDesc.pl or nil
                                     }))
                                     })
                                 end
                                 end
                             end
                             end
Linha 345: Linha 195:
         end
         end


         -- suborder do módulo (se existir pra esta skill M)
         -- suborder do módulo
         if a.M and tonumber(a.M) then
         if a.M and tonumber(a.M) then
             local m = tonumber(a.M)
             local m = tonumber(a.M)
Linha 357: Linha 207:
     end
     end


     -- ===== Weapon: parsear |weapon= e buscar desc do módulo =====
     -- Weapon
     do
     do
         local weaponPacked = trim(a.weapon or "")
         local weaponPacked = trim(a.weapon or "")
         if weaponPacked ~= "" then
         if weaponPacked ~= "" then
            -- Tentar parsear o JSON do {{Weapon}}
             local ok, weaponData = pcall(mw.text.jsonDecode, weaponPacked)
             local ok, weaponData = pcall(mw.text.jsonDecode, weaponPacked)
             if ok and type(weaponData) == "table" then
             if ok and type(weaponData) == "table" then
                -- Buscar descrição da arma no módulo (skills[key].weapon.desc)
                 local weaponDesc_i18n = {}
                 local weaponDesc_i18n = {}
                 if a.M and tonumber(a.M) then
                 if a.M and tonumber(a.M) then
Linha 381: Linha 229:
                     end
                     end
                 end
                 end
                -- Montar objeto weapon com atributos do template + desc do módulo
                 local weaponObj = {
                 local weaponObj = {
                     icon = weaponData.icon or "Nada.png",
                     icon = weaponData.icon or "Nada.png",
Linha 398: Linha 244:
                     weaponObj.descPl = weaponDesc_i18n.pl
                     weaponObj.descPl = weaponDesc_i18n.pl
                 end
                 end
                 obj.weapon = weaponObj
                 obj.weapon = weaponObj
             end
             end
Linha 407: Linha 252:
end
end


-- ===== Emote (sem M) =====
-- ===== Emote =====


function p.emote(frame)
function p.emote(frame)
Linha 421: Linha 266:
end
end


-- ===== Subskill (nome n EXATO do suborder; agrega desc_i18n do módulo + atributos locais) =====
-- ===== Subskill =====
-- Gera JSON simples. Herança de atributos é feita no JavaScript.


function p.subskill(frame)
function p.subskill(frame)
     local a = safeArgs(frame)
     local a = safeArgs(frame)
     local lang = trim((a.lang or (frame:getParent() and frame:getParent().args.lang) or "pt"):lower())
     local lang = trim((a.lang or (frame:getParent() and frame:getParent().args.lang) or "pt"):lower())
    -- Nome: pode vir de |n= ou inferido de |M= via módulo
     local n = trim(a.n or "")
     local n = trim(a.n or "")
    local refM = trim(a.m or a.M or "")
    if n == "" and refM ~= "" then
        local char = resolveCharFromFrames(frame, a)
        local data = requireCharacterModule(char) or {}
        local order = data.order or {}
        local idx = tonumber(refM)
        if idx and order[idx] then
            n = trim(order[idx])
        end
    end
     if n == "" then
     if n == "" then
         -- vamos tentar resolver usando o índice M e a ordem do módulo
         return mw.text.jsonEncode({})
        local tentativeIndex = tonumber(a.m or a.M)
        if tentativeIndex then
            local char = resolveCharFromFrames(frame, a)
            local data = requireCharacterModule(char) or {}
            local order = data.order or {}
            local inferred = trim(order[tentativeIndex] or "")
            if inferred ~= "" then
                n = inferred
            end
        end
        if n == "" then
            return mw.text.jsonEncode({})
        end
     end
     end


    -- Usa resolveCharFromFrames que busca recursivamente em todos os parent frames
     local char = resolveCharFromFrames(frame, a)
     local char = resolveCharFromFrames(frame, a)
     local data = requireCharacterModule(char) or {}
     local data = requireCharacterModule(char) or {}


     -- HERANÇA AUTOMÁTICA: busca desc com fallback
     -- Busca descrição do módulo .lua
     local desc_i18n = {}
     local desc_i18n = {}
     local foundDesc = nil
     local foundDesc = nil
    -- Primeiro tenta encontrar em subskills (comportamento original)
     for _, key in ipairs(data.order or {}) do
     for _, key in ipairs(data.order or {}) do
         local sk = (data.skills or {})[key]
         local sk = (data.skills or {})[key]
Linha 463: Linha 307:
         end
         end
     end
     end
    -- Se não encontrou, aplica herança automática
     if not foundDesc then
     if not foundDesc then
         foundDesc = resolveDescWithInheritance(data, n, nil)
         foundDesc = resolveDescWithInheritance(data, n, nil)
     end
     end
    -- Coloriza as descrições encontradas
     if foundDesc then
     if foundDesc then
         local langs = { "pt", "en", "es", "pl" }
         local langs = { "pt", "en", "es", "pl" }
Linha 480: Linha 320:
     end
     end


    -- Parse flags CSV
     local flagsArr = parseFlags(a.flags)
     local flagsArr = parseFlags(a.flags)


     local refM = trim(a.m or a.M or "")
     -- Objeto simples: só o que foi passado + refM para herança no JS
    local metaIndex = tonumber(refM)
    local inherited = fetchSkillMeta(char, metaIndex, n)
 
     local obj = {
     local obj = {
         name = n,
         name = n,
         icon = normIcon(a.icon) or (inherited and inherited.icon) or "Nada.png",
         icon = trim(a.icon or ""),
         level = normExcept(a.level, "NIVEL") or (inherited and inherited.level) or "",
         level = trim(a.level or ""),
         video = norm(a.video) or (inherited and inherited.video) or "",
         video = trim(a.video or ""),
         energy = nz(a.energy) or (inherited and inherited.energy),
         energy = nz(a.energy),
         powerpve = nz(a.powerpve) or (inherited and inherited.powerpve),
         powerpve = nz(a.powerpve),
         powerpvp = nz(a.powerpvp) or (inherited and inherited.powerpvp),
         powerpvp = nz(a.powerpvp),
         cooldown = nz(a.cooldown) or (inherited and inherited.cooldown),
         cooldown = nz(a.cooldown),
         back = (trim(a.back or "") == "yes" or trim(a.back or "") == "true") and true or nil
         back = (trim(a.back or "") == "yes" or trim(a.back or "") == "true") and true or nil
     }
     }
Linha 502: Linha 338:
         obj.refM = refM
         obj.refM = refM
     end
     end
     if #flagsArr > 0 then
     if #flagsArr > 0 then
         obj.flags = flagsArr
         obj.flags = flagsArr
     end
     end
     if next(desc_i18n) ~= nil then
     if next(desc_i18n) ~= nil then
         obj.desc_i18n = desc_i18n
         obj.desc_i18n = desc_i18n
Linha 515: Linha 349:
     end
     end


     -- Nested subs support: allow |subs= inside a subskill
     -- Nested subs
     do
     do
         local subsPacked = a.subs or ""
         local subsPacked = a.subs or ""
Linha 531: Linha 365:


     return mw.text.jsonEncode(obj)
     return mw.text.jsonEncode(obj)
end
function p.registerSkillMeta(frame)
    local a = safeArgs(frame)
    local char = resolveCharFromFrames(frame, a)
    local metaIndex = tonumber(a.M)
    if not metaIndex then
        return ""
    end
    storeSkillMeta(char, metaIndex, nil, {
        icon = normIcon(a.icon),
        level = normExcept(a.level, "NIVEL"),
        energy = norm(a.energy),
        powerpve = norm(a.powerpve),
        powerpvp = norm(a.powerpvp),
        cooldown = norm(a.cooldown),
        video = norm(a.video)
    })
    return ""
end
end


return p
return p

Edição das 16h57min de 30 de novembro de 2025

A documentação para este módulo pode ser criada em Módulo:Info.Skills/doc

-- Módulo:Info.Skills — skill(), subskill(), emote()
local p = {}

local utils = require("Módulo:Info.Utils")
local trim = utils.trim
local safeArgs = utils.safeArgs
local collectJsonObjects = utils.collectJsonObjects
local requireCharacterModule = utils.requireCharacterModule
local resolveCharFromFrames = utils.resolveCharFromFrames
local colorize = utils.colorize
local nz = utils.nz
local parseFlags = utils.parseFlags

-- ===== Herança automática de descrições =====

local function findDescInSkills(data, skillName)
    if not data or not data.skills then return nil end
    local sk = data.skills[skillName]
    if sk and type(sk.desc) == "table" then
        return sk.desc
    end
    return nil
end

local function findDescInAnySubskills(data, skillName)
    if not data or not data.skills then return nil end
    for _, sk in pairs(data.skills) do
        if type(sk) == "table" and type(sk.subskills) == "table" then
            local sub = sk.subskills[skillName]
            if sub and type(sub.desc) == "table" then
                return sub.desc
            end
        end
    end
    return nil
end

local function resolveDescWithInheritance(data, skillName, directDesc)
    if directDesc and type(directDesc) == "table" and next(directDesc) then
        return directDesc
    end
    local fromSkills = findDescInSkills(data, skillName)
    if fromSkills then
        return fromSkills
    end
    local fromSubskills = findDescInAnySubskills(data, skillName)
    if fromSubskills then
        return fromSubskills
    end
    return nil
end

-- ===== Weapon =====

function p.weapon(frame)
    local a = safeArgs(frame)
    local obj = {
        icon = (trim(a.icon or "") ~= "" and a.icon or "Nada.png"),
        energy = nz(a.energy),
        powerpve = nz(a.powerpve),
        powerpvp = nz(a.powerpvp),
        cooldown = nz(a.cooldown),
        video = a.video or ""
    }
    return mw.text.jsonEncode(obj)
end

-- ===== Skill (com M) =====

function p.skill(frame)
    local a = safeArgs(frame)
    local lang = trim((a.lang or (frame:getParent() and frame:getParent().args.lang) or "pt"):lower())
    local char = resolveCharFromFrames(frame, a)
    local data = requireCharacterModule(char) or {}

    local name, desc_i18n = nil, {}
    if a.M and trim(a.M) ~= "" then
        local m = tonumber(a.M) or 0
        local order = data.order or {}
        local key = order[m] or ""
        local sk = (data.skills or {})[key] or {}
        name = trim(sk.name or key or "")
        if type(sk.desc) == "table" then
            local langs = { "pt", "en", "es", "pl" }
            for _, code in ipairs(langs) do
                local d = sk.desc[code]
                if d and trim(d) ~= "" then
                    desc_i18n[code] = colorize(d)
                end
            end
        elseif sk.desc and trim(sk.desc) ~= "" then
            desc_i18n["pt"] = colorize(sk.desc)
        end
    end

    local chosen = desc_i18n[lang] or desc_i18n["pt"] or ""
    local flagsArr = parseFlags(a.flags)

    local obj = {
        icon = (trim(a.icon or "") ~= "" and a.icon or "Nada.png"),
        level = (trim(a.level or "") ~= "" and a.level or "NIVEL"),
        energy = nz(a.energy),
        powerpve = nz(a.powerpve),
        powerpvp = nz(a.powerpvp),
        cooldown = nz(a.cooldown),
        video = a.video or "",
        name = name
    }
    if #flagsArr > 0 then
        obj.flags = flagsArr
    end
    if chosen ~= "" then
        obj.desc = chosen
    end
    if next(desc_i18n) ~= nil then
        obj.desc_i18n = desc_i18n
        obj.descPt = desc_i18n.pt
        obj.descEn = desc_i18n.en
        obj.descEs = desc_i18n.es
        obj.descPl = desc_i18n.pl
    end

    -- Subskills vindas via |subs=
    do
        local subsPacked = a.subs or ""
        local subsArr = {}
        for _, chunk in ipairs(collectJsonObjects(subsPacked)) do
            local ok, sk = pcall(mw.text.jsonDecode, chunk)
            if ok and type(sk) == "table" then
                table.insert(subsArr, sk)
            end
        end
        -- Fallback: subskills do módulo .lua
        if #subsArr == 0 and a.M and tonumber(a.M) then
            local m = tonumber(a.M)
            local order = (data and data.order) or {}
            local key = order[m] or ""
            local sk = (data and data.skills and data.skills[key]) or {}
            local suborder = type(sk) == "table" and sk.suborder or nil
            local subskills = type(sk) == "table" and sk.subskills or nil
            if type(suborder) == "table" and type(subskills) == "table" then
                for _, n in ipairs(suborder) do
                    local sub = subskills[n]
                    if type(sub) == "table" then
                        local resolvedDesc = resolveDescWithInheritance(data, n, sub.desc)
                        local subObj = {
                            n = n,
                            name = n,
                            icon = sub.icon or "",
                            level = sub.level or "",
                            video = sub.video or "",
                            energy = sub.energy,
                            powerpve = sub.powerpve,
                            powerpvp = sub.powerpvp,
                            cooldown = sub.cooldown,
                            desc_i18n = resolvedDesc,
                            descPt = resolvedDesc and resolvedDesc.pt or nil,
                            descEn = resolvedDesc and resolvedDesc.en or nil,
                            descEs = resolvedDesc and resolvedDesc.es or nil,
                            descPl = resolvedDesc and resolvedDesc.pl or nil
                        }
                        if type(sub.suborder) == "table" and type(sub.subskills) == "table" then
                            local nested = {}
                            for _, nn in ipairs(sub.suborder) do
                                local s2 = sub.subskills[nn]
                                if type(s2) == "table" then
                                    local nestedDesc = resolveDescWithInheritance(data, nn, s2.desc)
                                    table.insert(nested, {
                                        n = nn,
                                        name = nn,
                                        icon = s2.icon or "",
                                        level = s2.level or "",
                                        video = s2.video or "",
                                        energy = s2.energy,
                                        powerpve = s2.powerpve,
                                        powerpvp = s2.powerpvp,
                                        cooldown = s2.cooldown,
                                        desc_i18n = nestedDesc,
                                        descPt = nestedDesc and nestedDesc.pt or nil,
                                        descEn = nestedDesc and nestedDesc.en or nil,
                                        descEs = nestedDesc and nestedDesc.es or nil,
                                        descPl = nestedDesc and nestedDesc.pl or nil
                                    })
                                end
                            end
                            if #nested > 0 then subObj.subs = nested end
                        end
                        table.insert(subsArr, subObj)
                    end
                end
            end
        end
        if #subsArr > 0 then
            obj.subs = subsArr
        end

        -- suborder do módulo
        if a.M and tonumber(a.M) then
            local m = tonumber(a.M)
            local order = (data and data.order) or {}
            local key = order[m] or ""
            local sk = (data and data.skills and data.skills[key]) or {}
            if type(sk) == "table" and type(sk.suborder) == "table" then
                obj.suborder = sk.suborder
            end
        end
    end

    -- Weapon
    do
        local weaponPacked = trim(a.weapon or "")
        if weaponPacked ~= "" then
            local ok, weaponData = pcall(mw.text.jsonDecode, weaponPacked)
            if ok and type(weaponData) == "table" then
                local weaponDesc_i18n = {}
                if a.M and tonumber(a.M) then
                    local m = tonumber(a.M)
                    local order = (data and data.order) or {}
                    local key = order[m] or ""
                    local sk = (data and data.skills and data.skills[key]) or {}
                    if type(sk) == "table" and type(sk.weapon) == "table" and type(sk.weapon.desc) == "table" then
                        local langs = { "pt", "en", "es", "pl" }
                        for _, code in ipairs(langs) do
                            local d = sk.weapon.desc[code]
                            if d and trim(d) ~= "" then
                                weaponDesc_i18n[code] = colorize(d)
                            end
                        end
                    end
                end
                local weaponObj = {
                    icon = weaponData.icon or "Nada.png",
                    energy = weaponData.energy,
                    powerpve = weaponData.powerpve,
                    powerpvp = weaponData.powerpvp,
                    cooldown = weaponData.cooldown,
                    video = weaponData.video or ""
                }
                if next(weaponDesc_i18n) ~= nil then
                    weaponObj.desc_i18n = weaponDesc_i18n
                    weaponObj.descPt = weaponDesc_i18n.pt
                    weaponObj.descEn = weaponDesc_i18n.en
                    weaponObj.descEs = weaponDesc_i18n.es
                    weaponObj.descPl = weaponDesc_i18n.pl
                end
                obj.weapon = weaponObj
            end
        end
    end

    return mw.text.jsonEncode(obj)
end

-- ===== Emote =====

function p.emote(frame)
    local a = safeArgs(frame)
    local obj = {
        name = "Emote",
        desc = "",
        icon = (trim(a.icon or "") ~= "" and a.icon or "Nada.png"),
        level = (trim(a.level or "") ~= "" and a.level or "NIVEL"),
        video = a.video or ""
    }
    return mw.text.jsonEncode(obj)
end

-- ===== Subskill =====
-- Gera JSON simples. Herança de atributos é feita no JavaScript.

function p.subskill(frame)
    local a = safeArgs(frame)
    local lang = trim((a.lang or (frame:getParent() and frame:getParent().args.lang) or "pt"):lower())

    -- Nome: pode vir de |n= ou inferido de |M= via módulo
    local n = trim(a.n or "")
    local refM = trim(a.m or a.M or "")

    if n == "" and refM ~= "" then
        local char = resolveCharFromFrames(frame, a)
        local data = requireCharacterModule(char) or {}
        local order = data.order or {}
        local idx = tonumber(refM)
        if idx and order[idx] then
            n = trim(order[idx])
        end
    end

    if n == "" then
        return mw.text.jsonEncode({})
    end

    local char = resolveCharFromFrames(frame, a)
    local data = requireCharacterModule(char) or {}

    -- Busca descrição do módulo .lua
    local desc_i18n = {}
    local foundDesc = nil
    for _, key in ipairs(data.order or {}) do
        local sk = (data.skills or {})[key]
        if type(sk) == "table" and type(sk.subskills) == "table" then
            local sub = sk.subskills[n]
            if sub and type(sub.desc) == "table" then
                foundDesc = sub.desc
                break
            end
        end
    end
    if not foundDesc then
        foundDesc = resolveDescWithInheritance(data, n, nil)
    end
    if foundDesc then
        local langs = { "pt", "en", "es", "pl" }
        for _, code in ipairs(langs) do
            local d = foundDesc[code]
            if d and trim(d) ~= "" then
                desc_i18n[code] = colorize(d)
            end
        end
    end

    local flagsArr = parseFlags(a.flags)

    -- Objeto simples: só o que foi passado + refM para herança no JS
    local obj = {
        name = n,
        icon = trim(a.icon or ""),
        level = trim(a.level or ""),
        video = trim(a.video or ""),
        energy = nz(a.energy),
        powerpve = nz(a.powerpve),
        powerpvp = nz(a.powerpvp),
        cooldown = nz(a.cooldown),
        back = (trim(a.back or "") == "yes" or trim(a.back or "") == "true") and true or nil
    }

    if refM ~= "" then
        obj.refM = refM
    end
    if #flagsArr > 0 then
        obj.flags = flagsArr
    end
    if next(desc_i18n) ~= nil then
        obj.desc_i18n = desc_i18n
        obj.descPt = desc_i18n.pt
        obj.descEn = desc_i18n.en
        obj.descEs = desc_i18n.es
        obj.descPl = desc_i18n.pl
    end

    -- Nested subs
    do
        local subsPacked = a.subs or ""
        local subsArr = {}
        for _, chunk in ipairs(collectJsonObjects(subsPacked)) do
            local ok, sub = pcall(mw.text.jsonDecode, chunk)
            if ok and type(sub) == "table" then
                table.insert(subsArr, sub)
            end
        end
        if #subsArr > 0 then
            obj.subs = subsArr
        end
    end

    return mw.text.jsonEncode(obj)
end

return p