Módulo:Info.Skills

De Wiki Gla
Revisão de 20h20min de 30 de novembro de 2025 por Gurren1 (discussão | contribs)
Ir para navegação Ir para pesquisar

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

-- ===== Cache de metadados das skills para herança dos Subskills =====
local SkillMetaStore = {}

local function currentPageKey()
    local title = mw.title.getCurrentTitle()
    return title and title.fullText or "__page__"
end

local function metaBucket(char)
    local key = currentPageKey() .. "::" .. (trim(char or "") ~= "" and trim(char) or "__char__")
    if not SkillMetaStore[key] then
        SkillMetaStore[key] = { byIndex = {}, byName = {} }
    end
    return SkillMetaStore[key]
end

local function storeSkillMeta(char, index, name, data)
    if not data then return end
    local bucket = metaBucket(char)
    if index then
        local idx = tonumber(index) or index
        bucket.byIndex[idx] = data
    end
    if name and trim(name) ~= "" then
        bucket.byName[trim(name)] = data
    end
end

local function fetchSkillMeta(char, ref)
    if not ref then return nil end
    local bucket = metaBucket(char)
    local idxNum = tonumber(ref)
    if idxNum and bucket.byIndex[idxNum] then
        return bucket.byIndex[idxNum]
    end
    if bucket.byIndex[ref] then
        return bucket.byIndex[ref]
    end
    local refName = trim(ref)
    if refName ~= "" and bucket.byName[refName] then
        return bucket.byName[refName]
    end
    return nil
end

local function inheritSubFromMeta(char, sub)
    if type(sub) ~= "table" then return sub end
    local ref = sub.refM or sub.M or sub.m or sub.name or sub.n
    if not ref or trim(ref) == "" then
        return sub
    end
    local meta = fetchSkillMeta(char, ref)
    if not meta then
        return sub
    end

    local function pick(current, fallback)
        if current == false or current == 0 or current == "0" then
            return current
        end
        if current ~= nil and trim(tostring(current)) ~= "" then
            return current
        end
        return fallback
    end

    local hydrated = {}
    for k, v in pairs(sub) do
        hydrated[k] = v
    end
    hydrated.name = pick(sub.name or sub.n, meta.name)
    hydrated.icon = pick(sub.icon, meta.icon)
    hydrated.level = pick(sub.level, meta.level)
    hydrated.video = pick(sub.video, meta.video)
    hydrated.energy = pick(sub.energy, meta.energy)
    hydrated.powerpve = pick(sub.powerpve, meta.powerpve)
    hydrated.powerpvp = pick(sub.powerpvp, meta.powerpvp)
    hydrated.cooldown = pick(sub.cooldown, meta.cooldown)

    hydrated.desc = pick(sub.desc, meta.desc)
    hydrated.descPt = pick(sub.descPt, meta.descPt)
    hydrated.descEn = pick(sub.descEn, meta.descEn)
    hydrated.descEs = pick(sub.descEs, meta.descEs)
    hydrated.descPl = pick(sub.descPl, meta.descPl)

    if type(sub.desc_i18n) == "table" then
        hydrated.desc_i18n = sub.desc_i18n
    else
        hydrated.desc_i18n = {
            pt = hydrated.descPt,
            en = hydrated.descEn,
            es = hydrated.descEs,
            pl = hydrated.descPl
        }
    end

    if type(sub.subs) == "table" then
        hydrated.subs = {}
        for i, nested in ipairs(sub.subs) do
            hydrated.subs[i] = inheritSubFromMeta(char, nested)
        end
    end

    return hydrated
end

-- ===== 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

    -- Registra metadados desta skill para herança futura
    if a.M and trim(a.M) ~= "" then
        local metaData = {
            name = obj.name or name or "",
            icon = obj.icon,
            level = obj.level,
            energy = obj.energy,
            powerpve = obj.powerpve,
            powerpvp = obj.powerpvp,
            cooldown = obj.cooldown,
            video = obj.video,
            desc = obj.desc,
            descPt = obj.descPt,
            descEn = obj.descEn,
            descEs = obj.descEs,
            descPl = obj.descPl
        }
        storeSkillMeta(char, tonumber(a.M) or a.M, metaData.name, metaData)

        -- Se tem |S=, registra também com essa chave para herança em Subskills
        local sKey = trim(a.S or a.s or "")
        if sKey ~= "" then
            storeSkillMeta(char, sKey, metaData.name, metaData)
        end
    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
                -- Se tem refM, aplica herança do cache (que já foi populado pelos Skills anteriores)
                local refM = sk.refM or sk.M or sk.m
                if refM and trim(tostring(refM)) ~= "" then
                    local mainMeta = fetchSkillMeta(char, refM)
                    if mainMeta then
                        -- Preenche nome se não tiver
                        if (not sk.name or trim(sk.name) == "") and (not sk.n or trim(sk.n) == "") then
                            sk.name = mainMeta.name
                            sk.n = mainMeta.name
                        end
                        -- Herda atributos vazios
                        if not sk.icon or trim(sk.icon) == "" then sk.icon = mainMeta.icon end
                        if not sk.level or trim(tostring(sk.level)) == "" then sk.level = mainMeta.level end
                        if not sk.video or trim(sk.video) == "" then sk.video = mainMeta.video end
                        if sk.energy == nil then sk.energy = mainMeta.energy end
                        if sk.powerpve == nil then sk.powerpve = mainMeta.powerpve end
                        if sk.powerpvp == nil then sk.powerpvp = mainMeta.powerpvp end
                        if sk.cooldown == nil then sk.cooldown = mainMeta.cooldown end
                        -- Herda descrição
                        if not sk.desc_i18n and not sk.descPt then
                            sk.descPt = mainMeta.descPt
                            sk.descEn = mainMeta.descEn
                            sk.descEs = mainMeta.descEs
                            sk.descPl = mainMeta.descPl
                            sk.desc_i18n = {
                                pt = mainMeta.descPt,
                                en = mainMeta.descEn,
                                es = mainMeta.descEs,
                                pl = mainMeta.descPl
                            }
                        end
                    end
                end
                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)

                        -- Se tem inherit, busca atributos da skill principal referenciada
                        local inheritIdx = sub.inherit
                        local inheritedIcon, inheritedLevel, inheritedVideo = "", "", ""
                        local inheritedEnergy, inheritedPvE, inheritedPvP, inheritedCD = nil, nil, nil, nil

                        if inheritIdx and tonumber(inheritIdx) then
                            local mainSkillMeta = fetchSkillMeta(char, inheritIdx)
                            if mainSkillMeta then
                                inheritedIcon = mainSkillMeta.icon or ""
                                inheritedLevel = mainSkillMeta.level or ""
                                inheritedVideo = mainSkillMeta.video or ""
                                inheritedEnergy = mainSkillMeta.energy
                                inheritedPvE = mainSkillMeta.powerpve
                                inheritedPvP = mainSkillMeta.powerpvp
                                inheritedCD = mainSkillMeta.cooldown
                                -- Se não tem descrição própria, herda da skill principal
                                if not resolvedDesc then
                                    resolvedDesc = {
                                        pt = mainSkillMeta.descPt,
                                        en = mainSkillMeta.descEn,
                                        es = mainSkillMeta.descEs,
                                        pl = mainSkillMeta.descPl
                                    }
                                end
                            end
                        end

                        -- Coloriza as descrições
                        local desc_i18n = {}
                        if resolvedDesc then
                            local langs = { "pt", "en", "es", "pl" }
                            for _, code in ipairs(langs) do
                                local d = resolvedDesc[code]
                                if d and trim(d) ~= "" then
                                    desc_i18n[code] = colorize(d)
                                end
                            end
                        end

                        local subObj = {
                            n = n,
                            name = n,
                            icon = (sub.icon and sub.icon ~= "") and sub.icon or inheritedIcon,
                            level = (sub.level and sub.level ~= "") and tostring(sub.level) or inheritedLevel,
                            video = (sub.video and sub.video ~= "") and sub.video or inheritedVideo,
                            energy = sub.energy or inheritedEnergy,
                            powerpve = sub.powerpve or inheritedPvE,
                            powerpvp = sub.powerpvp or inheritedPvP,
                            cooldown = sub.cooldown or inheritedCD,
                            desc_i18n = next(desc_i18n) and desc_i18n or nil,
                            descPt = desc_i18n.pt or nil,
                            descEn = desc_i18n.en or nil,
                            descEs = desc_i18n.es or nil,
                            descPl = desc_i18n.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
            for i, sub in ipairs(subsArr) do
                subsArr[i] = inheritSubFromMeta(char, sub)
            end
            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 COMPLETA de atributos é feita no JavaScript.
-- Se |M= for passado sem |n=, retorna apenas refM para o JS resolver.

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

    local n = trim(a.n or "")
    local refM = trim(a.m or a.M or "")
    local refS = trim(a.S or a.s or "")

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

    -- Se tem |S=, busca do cache e herda atributos
    if refS ~= "" then
        local meta = fetchSkillMeta(char, refS)
        if meta then
            -- Herda atributos da skill referenciada, sobrescreve só o que foi passado
            local function pick(explicit, inherited)
                if explicit ~= nil and trim(tostring(explicit)) ~= "" then
                    return explicit
                end
                return inherited
            end

            -- Busca descrição: primeiro do módulo pelo nome herdado, depois usa a da skill
            local inheritedName = meta.name or ""
            local desc_i18n = {}
            local foundDesc = resolveDescWithInheritance(data, inheritedName, nil)
            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
            -- Se não achou no módulo, usa descrição da skill original
            if not next(desc_i18n) then
                desc_i18n = {
                    pt = meta.descPt,
                    en = meta.descEn,
                    es = meta.descEs,
                    pl = meta.descPl
                }
            end

            local obj = {
                name = pick(a.n, inheritedName),
                icon = pick(a.icon, meta.icon),
                level = pick(a.level, meta.level),
                video = pick(a.video, meta.video),
                energy = nz(a.energy) or meta.energy,
                powerpve = nz(a.powerpve) or meta.powerpve,
                powerpvp = nz(a.powerpvp) or meta.powerpvp,
                cooldown = nz(a.cooldown) or meta.cooldown,
                back = (trim(a.back or "") == "yes" or trim(a.back or "") == "true") and true or nil,
                refS = refS
            }

            local flagsArr = parseFlags(a.flags)
            if #flagsArr > 0 then
                obj.flags = flagsArr
            end
            if next(desc_i18n) 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

            return mw.text.jsonEncode(obj)
        end
    end

    -- Se só tem refM (sem nome), retorna objeto mínimo para o JS fazer herança completa
    if n == "" and refM ~= "" then
        local obj = {
            refM = refM,
            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
        }
        local flagsArr = parseFlags(a.flags)
        if #flagsArr > 0 then
            obj.flags = flagsArr
        end
        return mw.text.jsonEncode(obj)
    end

    -- Se não tem nome nem refM nem refS, retorna vazio
    if n == "" then
        return mw.text.jsonEncode({})
    end

    -- 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 com nome explícito
    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