Mudanças entre as edições de "Módulo:Info.Skills"
Ir para navegação
Ir para pesquisar
m |
m |
||
| (21 revisões intermediárias pelo mesmo usuário não estão sendo mostradas) | |||
| Linha 96: | Linha 96: | ||
hydrated.descEs = pick(sub.descEs, meta.descEs) | hydrated.descEs = pick(sub.descEs, meta.descEs) | ||
hydrated.descPl = pick(sub.descPl, meta.descPl) | hydrated.descPl = pick(sub.descPl, meta.descPl) | ||
-- PRESERVA weapon explicitamente (não herda de meta) | |||
if sub.weapon ~= nil then | |||
hydrated.weapon = sub.weapon | |||
end | |||
if sub.weaponPacked ~= nil then | |||
hydrated.weaponPacked = sub.weaponPacked | |||
end | |||
if type(sub.desc_i18n) == "table" then | if type(sub.desc_i18n) == "table" then | ||
| Linha 158: | Linha 166: | ||
-- ===== Weapon ===== | -- ===== Weapon ===== | ||
-- Retorna formato: icon~pve~pvp~cd~video~energy (separado por ~) | |||
-- Isso evita problemas com JSON dentro de templates aninhados | |||
function p.weapon(frame) | function p.weapon(frame) | ||
local a = safeArgs(frame) | local a = safeArgs(frame) | ||
local | local icon = trim(a.icon or "") ~= "" and a.icon or "Nada.png" | ||
local pve = trim(a.powerpve or "") | |||
local pvp = trim(a.powerpvp or "") | |||
powerpve = | local cd = trim(a.cooldown or "") | ||
local video = trim(a.video or "") | |||
cooldown = nz( | local energy = trim(a.energy or "") | ||
-- Formato: icon~pve~pvp~cd~video~energy | |||
return icon .. "~" .. pve .. "~" .. pvp .. "~" .. cd .. "~" .. video .. "~" .. energy | |||
end | |||
-- Helper para parsear formato de weapon (icon~pve~pvp~cd~video~energy) | |||
local function parseWeaponString(str) | |||
if not str or trim(str) == "" then return nil end | |||
str = trim(str) | |||
-- Se começa com {, é JSON (formato antigo) | |||
if str:sub(1, 1) == "{" then | |||
local ok, data = pcall(mw.text.jsonDecode, str) | |||
if ok and type(data) == "table" then | |||
local result = { | |||
icon = data.icon or "Nada.png" | |||
} | |||
-- Só inclui campos que existem (não inclui nil) | |||
if data.powerpve ~= nil then result.powerpve = data.powerpve end | |||
if data.powerpvp ~= nil then result.powerpvp = data.powerpvp end | |||
if data.cooldown ~= nil then result.cooldown = data.cooldown end | |||
if data.energy ~= nil then result.energy = data.energy end | |||
if data.video and trim(data.video) ~= "" then result.video = data.video end | |||
return result | |||
end | |||
return nil | |||
end | |||
-- Formato novo: icon~pve~pvp~cd~video~energy | |||
-- IMPORTANTE: precisa preservar campos vazios para manter ordem fixa | |||
local parts = {} | |||
-- Split manual que preserva campos vazios | |||
local start = 1 | |||
while true do | |||
local pos = str:find("~", start) | |||
if not pos then | |||
-- Último campo | |||
table.insert(parts, trim(str:sub(start))) | |||
break | |||
end | |||
table.insert(parts, trim(str:sub(start, pos - 1))) | |||
start = pos + 1 | |||
end | |||
-- Garante que temos pelo menos 6 campos (preenche com "" se necessário) | |||
while #parts < 6 do | |||
table.insert(parts, "") | |||
end | |||
-- Também aceita formato com vírgula: pve,pvp,cd,video | |||
if #parts < 2 then | |||
parts = {} | |||
for part in str:gmatch("[^,]+") do | |||
table.insert(parts, trim(part)) | |||
end | |||
if #parts >= 1 then | |||
local result = { | |||
icon = "Nada.png" | |||
} | |||
-- Só inclui campos que existem (não inclui nil) | |||
if nz(parts[1]) then result.powerpve = parts[1] end | |||
if nz(parts[2]) then result.powerpvp = parts[2] end | |||
if nz(parts[3]) then result.cooldown = parts[3] end | |||
if parts[4] and trim(parts[4]) ~= "" then result.video = parts[4] end | |||
if nz(parts[5]) then result.energy = parts[5] end | |||
return result | |||
end | |||
return nil | |||
end | |||
-- Garante que temos pelo menos as partes básicas (icon sempre existe) | |||
local result = { | |||
icon = nz(parts[1]) or "Nada.png" | |||
} | } | ||
return | -- Só inclui campos que existem (não inclui nil) | ||
if nz(parts[2]) then result.powerpve = parts[2] end | |||
if nz(parts[3]) then result.powerpvp = parts[3] end | |||
if nz(parts[4]) then result.cooldown = parts[4] end | |||
if parts[5] and trim(parts[5]) ~= "" then result.video = parts[5] end | |||
if nz(parts[6]) then result.energy = parts[6] end | |||
return result | |||
end | |||
local function assignWeaponFromArg(obj, weaponStr) | |||
if not obj then return end | |||
local packed = trim(weaponStr or "") | |||
if packed ~= "" then | |||
-- Remove quebras de linha e espaços extras que podem vir da expansão do MediaWiki | |||
packed = packed:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "") | |||
obj.weaponPacked = packed | |||
local parsed = parseWeaponString(packed) | |||
if parsed then | |||
obj.weapon = parsed | |||
end | |||
end | |||
end | |||
-- Normaliza uma tabela weapon vinda do módulo do personagem | |||
-- Aceita tanto weapon.desc quanto weapon.desc_i18n e sempre retorna weapon.desc_i18n | |||
local function normalizeWeaponTable(raw) | |||
if type(raw) ~= "table" then | |||
return nil | |||
end | |||
local w = {} | |||
-- Só inclui campos que existem (não inclui nil) | |||
if raw.icon then w.icon = raw.icon else w.icon = "Nada.png" end | |||
if raw.powerpve ~= nil then w.powerpve = raw.powerpve end | |||
if raw.powerpvp ~= nil then w.powerpvp = raw.powerpvp end | |||
if raw.cooldown ~= nil then w.cooldown = raw.cooldown end | |||
if raw.energy ~= nil then w.energy = raw.energy end | |||
-- Só inclui video se não estiver vazio | |||
if raw.video and trim(raw.video) ~= "" then | |||
w.video = raw.video | |||
end | |||
-- Aceita tanto raw.desc_i18n quanto raw.desc | |||
local src = nil | |||
if type(raw.desc_i18n) == "table" then | |||
src = raw.desc_i18n | |||
elseif type(raw.desc) == "table" then | |||
src = raw.desc | |||
end | |||
if src then | |||
local desc_i18n = {} | |||
for _, code in ipairs({ "pt", "en", "es", "pl" }) do | |||
local d = src[code] | |||
if d and trim(d) ~= "" then | |||
-- Usa a função colorize já existente no módulo | |||
desc_i18n[code] = colorize(d) | |||
end | |||
end | |||
if next(desc_i18n) ~= nil then | |||
w.desc_i18n = desc_i18n | |||
end | |||
end | |||
return w | |||
end | |||
local function ensureWeaponHydrated(sub) | |||
if type(sub) ~= "table" then return end | |||
-- Se weapon já é uma tabela válida, normaliza para garantir desc_i18n | |||
if sub.weapon and type(sub.weapon) == "table" and next(sub.weapon) ~= nil then | |||
local normalized = normalizeWeaponTable(sub.weapon) | |||
if normalized then | |||
sub.weapon = normalized | |||
end | |||
return -- Já está hidratado e normalizado | |||
end | |||
-- Se weapon é string, tenta parsear | |||
if sub.weapon and type(sub.weapon) ~= "table" then | |||
local parsed = parseWeaponString(sub.weapon) | |||
if parsed and next(parsed) ~= nil then | |||
sub.weapon = parsed | |||
else | |||
-- Se parse falhou mas tem weaponPacked, tenta depois | |||
if not sub.weaponPacked then | |||
sub.weaponPacked = sub.weapon | |||
end | |||
sub.weapon = nil | |||
end | |||
end | |||
-- Se não tem weapon mas tem weaponPacked, tenta parsear | |||
if not sub.weapon and sub.weaponPacked and trim(sub.weaponPacked) ~= "" then | |||
local parsed = parseWeaponString(sub.weaponPacked) | |||
if parsed and next(parsed) ~= nil then | |||
sub.weapon = parsed | |||
end | |||
end | |||
end | |||
local function normalizeSubTree(sub) | |||
if type(sub) ~= "table" then return end | |||
-- Preserva weapon antes de hidratar | |||
local hadWeapon = sub.weapon ~= nil | |||
local weaponBefore = sub.weapon | |||
ensureWeaponHydrated(sub) | |||
-- Se tinha weapon mas perdeu após hidratação, tenta recuperar do weaponPacked | |||
if hadWeapon and not sub.weapon and sub.weaponPacked then | |||
local parsed = parseWeaponString(sub.weaponPacked) | |||
if parsed and next(parsed) ~= nil then | |||
sub.weapon = parsed | |||
end | |||
end | |||
-- Se tinha weapon como objeto válido antes, preserva mesmo se ensureWeaponHydrated removeu | |||
if weaponBefore and type(weaponBefore) == "table" and next(weaponBefore) ~= nil and not sub.weapon then | |||
sub.weapon = weaponBefore | |||
end | |||
if type(sub.subs) == "table" then | |||
for _, nested in ipairs(sub.subs) do | |||
normalizeSubTree(nested) | |||
end | |||
end | |||
-- Só remove weaponPacked se weapon está válido | |||
if sub.weapon and type(sub.weapon) == "table" and next(sub.weapon) ~= nil then | |||
sub.weaponPacked = nil | |||
end | |||
end | end | ||
| Linha 305: | Linha 508: | ||
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 | ||
ensureWeaponHydrated(sk) | |||
local ref = sk.refM or sk.M or sk.m or sk.refS or sk.S or sk.s | local ref = sk.refM or sk.M or sk.m or sk.refS or sk.S or sk.s | ||
if ref and addedIndices[tostring(ref)] then | if ref and addedIndices[tostring(ref)] then | ||
| Linha 318: | Linha 522: | ||
if sk.powerpvp ~= nil then existing.powerpvp = sk.powerpvp end | if sk.powerpvp ~= nil then existing.powerpvp = sk.powerpvp end | ||
if sk.cooldown ~= nil then existing.cooldown = sk.cooldown end | if sk.cooldown ~= nil then existing.cooldown = sk.cooldown end | ||
if sk.weapon ~= nil then existing.weapon = sk.weapon end | |||
if sk.weaponPacked ~= nil then existing.weaponPacked = sk.weaponPacked end | |||
-- BUSCA WEAPON DO MÓDULO LUA para fazer merge | |||
-- Se weapon já existe mas não tem desc_i18n, busca do módulo para completar | |||
local subName = existing.name or existing.n or "" | |||
if subName ~= "" then | |||
for _, skillKey in ipairs(data.order or {}) do | |||
local skillData = (data.skills or {})[skillKey] | |||
if type(skillData) == "table" and type(skillData.subskills) == "table" then | |||
local moduleSub = skillData.subskills[subName] | |||
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then | |||
local moduleWeapon = normalizeWeaponTable(moduleSub.weapon) | |||
if moduleWeapon then | |||
if existing.weapon and type(existing.weapon) == "table" then | |||
-- Merge: preserva atributos do template, adiciona desc_i18n do módulo | |||
if moduleWeapon.desc_i18n and (not existing.weapon.desc_i18n) then | |||
existing.weapon.desc_i18n = moduleWeapon.desc_i18n | |||
end | |||
-- Se não tem icon no template, usa do módulo | |||
if (not existing.weapon.icon or existing.weapon.icon == "Nada.png") and moduleWeapon.icon then | |||
existing.weapon.icon = moduleWeapon.icon | |||
end | |||
-- Se não tem atributos no template, usa do módulo | |||
if existing.weapon.powerpve == nil and moduleWeapon.powerpve ~= nil then | |||
existing.weapon.powerpve = moduleWeapon.powerpve | |||
end | |||
if existing.weapon.powerpvp == nil and moduleWeapon.powerpvp ~= nil then | |||
existing.weapon.powerpvp = moduleWeapon.powerpvp | |||
end | |||
if existing.weapon.cooldown == nil and moduleWeapon.cooldown ~= nil then | |||
existing.weapon.cooldown = moduleWeapon.cooldown | |||
end | |||
if existing.weapon.energy == nil and moduleWeapon.energy ~= nil then | |||
existing.weapon.energy = moduleWeapon.energy | |||
end | |||
if (not existing.weapon.video or existing.weapon.video == "") and moduleWeapon.video then | |||
existing.weapon.video = moduleWeapon.video | |||
end | |||
else | |||
-- Se não tem weapon ainda, usa o do módulo | |||
existing.weapon = moduleWeapon | |||
end | |||
break | |||
end | |||
end | |||
end | |||
end | |||
end | |||
-- Garante que weapon está hidratado após merge | |||
ensureWeaponHydrated(existing) | |||
-- Marca como já processado para não adicionar de novo | -- Marca como já processado para não adicionar de novo | ||
sk._merged = true | sk._merged = true | ||
| Linha 326: | Linha 582: | ||
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 | ||
ensureWeaponHydrated(sk) | |||
-- Pula se já foi mergeado com um índice | -- Pula se já foi mergeado com um índice | ||
local ref = sk.refM or sk.M or sk.m or sk.refS or sk.S or sk.s | local ref = sk.refM or sk.M or sk.m or sk.refS or sk.S or sk.s | ||
| Linha 454: | Linha 711: | ||
end | end | ||
end | end | ||
-- BUSCA WEAPON DO MÓDULO LUA para fazer merge | |||
-- Se weapon já existe mas não tem desc_i18n, busca do módulo para completar | |||
local subName = sk.name or sk.n or "" | |||
if subName ~= "" then | |||
for _, skillKey in ipairs(data.order or {}) do | |||
local skillData = (data.skills or {})[skillKey] | |||
if type(skillData) == "table" and type(skillData.subskills) == "table" then | |||
local moduleSub = skillData.subskills[subName] | |||
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then | |||
local moduleWeapon = normalizeWeaponTable(moduleSub.weapon) | |||
if moduleWeapon then | |||
if sk.weapon and type(sk.weapon) == "table" then | |||
-- Merge: preserva atributos do template, adiciona desc_i18n do módulo | |||
if moduleWeapon.desc_i18n and (not sk.weapon.desc_i18n) then | |||
sk.weapon.desc_i18n = moduleWeapon.desc_i18n | |||
end | |||
-- Se não tem icon no template, usa do módulo | |||
if (not sk.weapon.icon or sk.weapon.icon == "Nada.png") and moduleWeapon.icon then | |||
sk.weapon.icon = moduleWeapon.icon | |||
end | |||
-- Se não tem atributos no template, usa do módulo | |||
if sk.weapon.powerpve == nil and moduleWeapon.powerpve ~= nil then | |||
sk.weapon.powerpve = moduleWeapon.powerpve | |||
end | |||
if sk.weapon.powerpvp == nil and moduleWeapon.powerpvp ~= nil then | |||
sk.weapon.powerpvp = moduleWeapon.powerpvp | |||
end | |||
if sk.weapon.cooldown == nil and moduleWeapon.cooldown ~= nil then | |||
sk.weapon.cooldown = moduleWeapon.cooldown | |||
end | |||
if sk.weapon.energy == nil and moduleWeapon.energy ~= nil then | |||
sk.weapon.energy = moduleWeapon.energy | |||
end | |||
if (not sk.weapon.video or sk.weapon.video == "") and moduleWeapon.video then | |||
sk.weapon.video = moduleWeapon.video | |||
end | |||
else | |||
-- Se não tem weapon ainda, usa o do módulo | |||
sk.weapon = moduleWeapon | |||
end | |||
break | |||
end | |||
end | |||
end | |||
end | |||
end | |||
-- Garante que weapon está normalizado antes de adicionar | |||
ensureWeaponHydrated(sk) | |||
table.insert(subsArr, sk) | table.insert(subsArr, sk) | ||
end | end | ||
| Linha 527: | Linha 834: | ||
descPl = desc_i18n.pl or nil | descPl = desc_i18n.pl or nil | ||
} | } | ||
-- INCLUI WEAPON do módulo Lua se existir | |||
if type(sub.weapon) == "table" then | |||
local weaponObj = normalizeWeaponTable(sub.weapon) | |||
if weaponObj then | |||
subObj.weapon = weaponObj | |||
end | |||
end | |||
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 533: | Linha 847: | ||
if type(s2) == "table" then | if type(s2) == "table" then | ||
local nestedDesc = resolveDescWithInheritance(data, nn, s2.desc) | local nestedDesc = resolveDescWithInheritance(data, nn, s2.desc) | ||
local nestedObj = { | |||
n = nn, | n = nn, | ||
name = nn, | name = nn, | ||
| Linha 548: | Linha 862: | ||
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 | ||
}) | } | ||
-- INCLUI WEAPON do nested se existir | |||
if type(s2.weapon) == "table" then | |||
local weaponObj = normalizeWeaponTable(s2.weapon) | |||
if weaponObj then | |||
nestedObj.weapon = weaponObj | |||
end | |||
end | |||
table.insert(nested, nestedObj) | |||
end | end | ||
end | end | ||
| Linha 561: | Linha 883: | ||
for i, sub in ipairs(subsArr) do | for i, sub in ipairs(subsArr) do | ||
subsArr[i] = inheritSubFromMeta(char, sub) | subsArr[i] = inheritSubFromMeta(char, sub) | ||
-- BUSCA WEAPON DO MÓDULO LUA se não tem weapon ainda (após herança) | |||
local hasWeapon = subsArr[i].weapon and ( | |||
(type(subsArr[i].weapon) == "table" and next(subsArr[i].weapon) ~= nil) or | |||
(type(subsArr[i].weapon) == "string" and trim(subsArr[i].weapon) ~= "") | |||
) | |||
if not hasWeapon then | |||
local subName = subsArr[i].name or subsArr[i].n or "" | |||
if subName ~= "" then | |||
for _, skillKey in ipairs(data.order or {}) do | |||
local skillData = (data.skills or {})[skillKey] | |||
if type(skillData) == "table" and type(skillData.subskills) == "table" then | |||
local moduleSub = skillData.subskills[subName] | |||
if moduleSub then | |||
-- DEBUG: Log temporário | |||
if subName == "Karmic Jishin" then | |||
-- Verifica se tem weapon | |||
end | |||
end | |||
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then | |||
local weaponObj = { | |||
icon = moduleSub.weapon.icon or "Nada.png", | |||
energy = moduleSub.weapon.energy, | |||
powerpve = moduleSub.weapon.powerpve, | |||
powerpvp = moduleSub.weapon.powerpvp, | |||
cooldown = moduleSub.weapon.cooldown, | |||
video = moduleSub.weapon.video or "" | |||
} | |||
if type(moduleSub.weapon.desc) == "table" then | |||
local weaponDesc = {} | |||
for _, code in ipairs({ "pt", "en", "es", "pl" }) do | |||
local d = moduleSub.weapon.desc[code] | |||
if d and trim(d) ~= "" then | |||
weaponDesc[code] = colorize(d) | |||
end | |||
end | |||
if next(weaponDesc) then | |||
weaponObj.desc_i18n = weaponDesc | |||
end | |||
end | |||
subsArr[i].weapon = weaponObj | |||
-- DEBUG: Log para confirmar que encontrou | |||
if subName == "Karmic Jishin" then | |||
-- Log temporário | |||
end | |||
break | |||
end | |||
end | |||
end | |||
end | |||
end | |||
normalizeSubTree(subsArr[i]) | |||
-- VERIFICAÇÃO FINAL: Garante que weapon está presente e completo antes de serializar | |||
-- Busca do módulo para fazer merge (mesmo se já tem weapon do template) | |||
local subName = subsArr[i].name or subsArr[i].n or "" | |||
if subName ~= "" then | |||
for _, skillKey in ipairs(data.order or {}) do | |||
local skillData = (data.skills or {})[skillKey] | |||
if type(skillData) == "table" and type(skillData.subskills) == "table" then | |||
local moduleSub = skillData.subskills[subName] | |||
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then | |||
local moduleWeapon = normalizeWeaponTable(moduleSub.weapon) | |||
if moduleWeapon then | |||
if subsArr[i].weapon and type(subsArr[i].weapon) == "table" then | |||
-- Merge: preserva atributos do template, adiciona desc_i18n do módulo | |||
if moduleWeapon.desc_i18n and (not subsArr[i].weapon.desc_i18n) then | |||
subsArr[i].weapon.desc_i18n = moduleWeapon.desc_i18n | |||
end | |||
-- Se não tem icon no template, usa do módulo | |||
if (not subsArr[i].weapon.icon or subsArr[i].weapon.icon == "Nada.png") and moduleWeapon.icon then | |||
subsArr[i].weapon.icon = moduleWeapon.icon | |||
end | |||
-- Se não tem atributos no template, usa do módulo | |||
if subsArr[i].weapon.powerpve == nil and moduleWeapon.powerpve ~= nil then | |||
subsArr[i].weapon.powerpve = moduleWeapon.powerpve | |||
end | |||
if subsArr[i].weapon.powerpvp == nil and moduleWeapon.powerpvp ~= nil then | |||
subsArr[i].weapon.powerpvp = moduleWeapon.powerpvp | |||
end | |||
if subsArr[i].weapon.cooldown == nil and moduleWeapon.cooldown ~= nil then | |||
subsArr[i].weapon.cooldown = moduleWeapon.cooldown | |||
end | |||
if subsArr[i].weapon.energy == nil and moduleWeapon.energy ~= nil then | |||
subsArr[i].weapon.energy = moduleWeapon.energy | |||
end | |||
if (not subsArr[i].weapon.video or subsArr[i].weapon.video == "") and moduleWeapon.video then | |||
subsArr[i].weapon.video = moduleWeapon.video | |||
end | |||
else | |||
-- Se não tem weapon ainda, usa o do módulo | |||
subsArr[i].weapon = moduleWeapon | |||
end | |||
break | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | end | ||
obj.subs = subsArr | obj.subs = subsArr | ||
-- SISTEMA UNIFICADO DE WEAPON PARA SUBSKILLS: Garante weapon normalizado e completo antes de serializar | |||
if type(obj.subs) == "table" then | |||
for i, sub in ipairs(obj.subs) do | |||
local subName = sub.name or sub.n or "" | |||
-- PASSO 1: Normaliza weapon existente | |||
ensureWeaponHydrated(sub) | |||
normalizeSubTree(sub) | |||
-- PASSO 2: Se weapon ainda é string, tenta parsear | |||
if sub.weapon and type(sub.weapon) ~= "table" then | |||
local parsed = parseWeaponString(sub.weapon) | |||
if parsed and next(parsed) ~= nil then | |||
sub.weapon = parsed | |||
else | |||
sub.weapon = nil | |||
end | |||
end | |||
-- PASSO 3: Busca weapon do módulo (sempre, para garantir merge completo) | |||
if subName ~= "" then | |||
for _, skillKey in ipairs(data.order or {}) do | |||
local skillData = (data.skills or {})[skillKey] | |||
if type(skillData) == "table" and type(skillData.subskills) == "table" then | |||
-- Tenta match exato primeiro | |||
local moduleSub = skillData.subskills[subName] | |||
-- Se não encontrou, tenta case-insensitive | |||
if not moduleSub then | |||
for k, v in pairs(skillData.subskills) do | |||
if type(k) == "string" and k:lower() == subName:lower() then | |||
moduleSub = v | |||
break | |||
end | |||
end | |||
end | |||
-- Se encontrou subskill no módulo com weapon, faz merge completo | |||
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then | |||
local moduleWeapon = normalizeWeaponTable(moduleSub.weapon) | |||
if moduleWeapon then | |||
if obj.subs[i].weapon and type(obj.subs[i].weapon) == "table" then | |||
-- MERGE: Combina weapon do template com weapon do módulo | |||
-- Descrição do módulo sempre tem prioridade (tem desc_i18n completo) | |||
if moduleWeapon.desc_i18n then | |||
obj.subs[i].weapon.desc_i18n = moduleWeapon.desc_i18n | |||
end | |||
-- Atributos: usa do módulo se não tem no template | |||
if not obj.subs[i].weapon.icon or obj.subs[i].weapon.icon == "Nada.png" then | |||
obj.subs[i].weapon.icon = moduleWeapon.icon | |||
end | |||
if obj.subs[i].weapon.powerpve == nil then | |||
obj.subs[i].weapon.powerpve = moduleWeapon.powerpve | |||
end | |||
if obj.subs[i].weapon.powerpvp == nil then | |||
obj.subs[i].weapon.powerpvp = moduleWeapon.powerpvp | |||
end | |||
if obj.subs[i].weapon.cooldown == nil then | |||
obj.subs[i].weapon.cooldown = moduleWeapon.cooldown | |||
end | |||
if obj.subs[i].weapon.energy == nil then | |||
obj.subs[i].weapon.energy = moduleWeapon.energy | |||
end | |||
if not obj.subs[i].weapon.video or obj.subs[i].weapon.video == "" then | |||
obj.subs[i].weapon.video = moduleWeapon.video | |||
end | |||
else | |||
-- Se não tem weapon ainda, usa o do módulo completo | |||
obj.subs[i].weapon = moduleWeapon | |||
end | |||
break | |||
end | |||
end | |||
end | |||
end | |||
end | |||
-- PASSO 4: Garante que weapon está normalizado e válido | |||
if obj.subs[i].weapon then | |||
ensureWeaponHydrated(obj.subs[i]) | |||
-- Remove weaponPacked se weapon está válido | |||
if type(obj.subs[i].weapon) == "table" and next(obj.subs[i].weapon) ~= nil then | |||
obj.subs[i].weaponPacked = nil | |||
-- Garante que desc_i18n existe se tem descrição | |||
if obj.subs[i].weapon.desc_i18n and type(obj.subs[i].weapon.desc_i18n) == "table" then | |||
-- Já está normalizado | |||
elseif obj.subs[i].weapon.desc and type(obj.subs[i].weapon.desc) == "table" then | |||
-- Converte desc para desc_i18n | |||
local desc_i18n = {} | |||
for _, code in ipairs({ "pt", "en", "es", "pl" }) do | |||
local d = obj.subs[i].weapon.desc[code] | |||
if d and trim(d) ~= "" then | |||
desc_i18n[code] = colorize(d) | |||
end | |||
end | |||
if next(desc_i18n) ~= nil then | |||
obj.subs[i].weapon.desc_i18n = desc_i18n | |||
end | |||
end | |||
else | |||
obj.subs[i].weapon = nil | |||
end | |||
end | |||
end | |||
end | |||
end | end | ||
| Linha 579: | Linha 1 102: | ||
-- Weapon | -- Weapon | ||
do | do | ||
assignWeaponFromArg(obj, a.weapon) | |||
if | if obj.weapon then | ||
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" then | |||
local normalized = normalizeWeaponTable(sk.weapon) | |||
if normalized then | |||
-- Preserva campos já existentes em obj.weapon se houver | |||
if obj.weapon then | |||
-- Merge inteligente: só sobrescreve se o campo do módulo não estiver vazio | |||
-- E só sobrescreve se o campo do template estiver vazio ou não existir | |||
for k, v in pairs(normalized) do | |||
-- Para video: SEMPRE preserva o do template se existir (não faz merge) | |||
if k == "video" then | |||
-- Se o template tem vídeo, preserva (não sobrescreve com módulo) | |||
if obj.weapon.video and trim(obj.weapon.video) ~= "" then | |||
-- Mantém o vídeo do template, não faz nada | |||
elseif v and v ~= "" then | |||
-- Só usa do módulo se template não tem | |||
obj.weapon.video = v | |||
end | |||
-- Para icon: preserva o do template se não for Nada.png | |||
elseif k == "icon" then | |||
if (not obj.weapon.icon or obj.weapon.icon == "" or obj.weapon.icon == "Nada.png") and v and v ~= "" and v ~= "Nada.png" then | |||
obj.weapon.icon = v | |||
end | |||
-- Para outros campos: só faz merge se o campo NÃO EXISTE no template (nil) | |||
-- Se o campo existe no template (mesmo que vazio), preserva o do template | |||
else | |||
-- IMPORTANTE: só faz merge se o campo é nil (não existe), não se está vazio | |||
if obj.weapon[k] == nil and v ~= nil and v ~= "" then | |||
obj.weapon[k] = v | |||
end | |||
end | |||
end | |||
-- Sempre adiciona desc_i18n do módulo se não tiver no template | |||
if normalized.desc_i18n and not obj.weapon.desc_i18n then | |||
obj.weapon.desc_i18n = normalized.desc_i18n | |||
end | end | ||
else | |||
obj.weapon = normalized | |||
end | |||
-- Adiciona campos descPt, descEn, etc. para compatibilidade | |||
if obj.weapon.desc_i18n then | |||
obj.weapon.descPt = obj.weapon.desc_i18n.pt | |||
obj.weapon.descEn = obj.weapon.desc_i18n.en | |||
obj.weapon.descEs = obj.weapon.desc_i18n.es | |||
obj.weapon.descPl = obj.weapon.desc_i18n.pl | |||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
| Linha 715: | Linha 1 256: | ||
-- Weapon para subskill | -- Weapon para subskill | ||
assignWeaponFromArg(obj, a.weapon) | |||
return mw.text.jsonEncode(obj) | return mw.text.jsonEncode(obj) | ||
| Linha 781: | Linha 1 309: | ||
end | end | ||
-- Weapon para subskill | -- Weapon para subskill | ||
assignWeaponFromArg(obj, a.weapon) | |||
return mw.text.jsonEncode(obj) | return mw.text.jsonEncode(obj) | ||
end | end | ||
| Linha 817: | Linha 1 332: | ||
end | end | ||
-- Weapon para subskill | -- Weapon para subskill | ||
assignWeaponFromArg(obj, a.weapon) | |||
return mw.text.jsonEncode(obj) | return mw.text.jsonEncode(obj) | ||
end | end | ||
| Linha 901: | Linha 1 403: | ||
local ok, sub = pcall(mw.text.jsonDecode, chunk) | local ok, sub = pcall(mw.text.jsonDecode, chunk) | ||
if ok and type(sub) == "table" then | if ok and type(sub) == "table" then | ||
ensureWeaponHydrated(sub) | |||
table.insert(subsArr, sub) | table.insert(subsArr, sub) | ||
end | end | ||
end | end | ||
if #subsArr > 0 then | if #subsArr > 0 then | ||
for _, sub in ipairs(subsArr) do | |||
-- BUSCA WEAPON DO MÓDULO LUA se não tem weapon ainda (para nested subs também) | |||
local hasWeapon = sub.weapon and ( | |||
(type(sub.weapon) == "table" and next(sub.weapon) ~= nil) or | |||
(type(sub.weapon) == "string" and trim(sub.weapon) ~= "") | |||
) | |||
if not hasWeapon then | |||
local subName = sub.name or sub.n or "" | |||
if subName ~= "" then | |||
for _, skillKey in ipairs(data.order or {}) do | |||
local skillData = (data.skills or {})[skillKey] | |||
if type(skillData) == "table" and type(skillData.subskills) == "table" then | |||
local moduleSub = skillData.subskills[subName] | |||
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then | |||
local weaponObj = normalizeWeaponTable(moduleSub.weapon) | |||
if weaponObj then | |||
sub.weapon = weaponObj | |||
break | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
normalizeSubTree(sub) | |||
end | |||
obj.subs = subsArr | obj.subs = subsArr | ||
end | end | ||
end | end | ||
-- Weapon para subskill | -- Weapon para subskill | ||
-- Usa {{Weapon|...}} que retorna formato: icon~pve~pvp~cd~video~energy | |||
do | do | ||
assignWeaponFromArg(obj, a.weapon) | |||
-- Busca descrição do weapon no módulo Lua (subskills) | |||
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.weapon) == "table" then | |||
local normalized = normalizeWeaponTable(sub.weapon) | |||
if normalized then | |||
-- Preserva campos já existentes em obj.weapon se houver | |||
if obj.weapon then | |||
-- Merge inteligente: só sobrescreve se o campo do módulo não estiver vazio | |||
-- E só sobrescreve se o campo do template estiver vazio ou não existir | |||
for k, v in pairs(normalized) do | |||
-- Para video: SEMPRE preserva o do template se existir (não faz merge) | |||
if k == "video" then | |||
-- Se o template tem vídeo, preserva (não sobrescreve com módulo) | |||
if obj.weapon.video and trim(obj.weapon.video) ~= "" then | |||
-- Mantém o vídeo do template, não faz nada | |||
elseif v and v ~= "" then | |||
-- Só usa do módulo se template não tem | |||
obj.weapon.video = v | |||
end | |||
-- Para icon: preserva o do template se não for Nada.png | |||
elseif k == "icon" then | |||
if (not obj.weapon.icon or obj.weapon.icon == "" or obj.weapon.icon == "Nada.png") and v and v ~= "" and v ~= "Nada.png" then | |||
obj.weapon.icon = v | |||
end | |||
-- Para outros campos: só faz merge se o campo NÃO EXISTE no template (nil) | |||
-- Se o campo existe no template (mesmo que vazio), preserva o do template | |||
else | |||
-- IMPORTANTE: só faz merge se o campo é nil (não existe), não se está vazio | |||
if obj.weapon[k] == nil and v ~= nil and v ~= "" then | |||
obj.weapon[k] = v | |||
end | |||
end | end | ||
end | end | ||
-- Sempre adiciona desc_i18n do módulo se não tiver no template | |||
if normalized.desc_i18n and not obj.weapon.desc_i18n then | |||
obj.weapon.desc_i18n = normalized.desc_i18n | |||
end | |||
else | |||
obj.weapon = normalized | |||
end | |||
-- Adiciona campos descPt, descEn, etc. para compatibilidade | |||
if obj.weapon.desc_i18n then | |||
obj.weapon.descPt = obj.weapon.desc_i18n.pt | |||
obj.weapon.descEn = obj.weapon.desc_i18n.en | |||
obj.weapon.descEs = obj.weapon.desc_i18n.es | |||
obj.weapon.descPl = obj.weapon.desc_i18n.pl | |||
end | end | ||
end | end | ||
break | |||
end | end | ||
end | |||
end | |||
if obj.weapon and type(obj.weapon) == "table" and next(obj.weapon) == nil then | |||
obj.weapon = nil | |||
end | |||
end | |||
obj.weapon = | -- GARANTIA FINAL: Normaliza weapon antes de serializar | ||
if obj.weapon then | |||
ensureWeaponHydrated(obj) | |||
-- Se weapon ainda é string ou inválido, tenta parsear | |||
if obj.weapon and type(obj.weapon) ~= "table" then | |||
local parsed = parseWeaponString(obj.weapon) | |||
if parsed and next(parsed) ~= nil then | |||
obj.weapon = parsed | |||
else | |||
obj.weapon = nil | |||
end | end | ||
end | |||
-- Remove weapon se estiver vazio | |||
if obj.weapon and type(obj.weapon) == "table" and next(obj.weapon) == nil then | |||
obj.weapon = nil | |||
end | end | ||
end | end | ||
Edição atual tal como às 17h47min de 5 de dezembro 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
-- ===== 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)
-- PRESERVA weapon explicitamente (não herda de meta)
if sub.weapon ~= nil then
hydrated.weapon = sub.weapon
end
if sub.weaponPacked ~= nil then
hydrated.weaponPacked = sub.weaponPacked
end
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 =====
-- Retorna formato: icon~pve~pvp~cd~video~energy (separado por ~)
-- Isso evita problemas com JSON dentro de templates aninhados
function p.weapon(frame)
local a = safeArgs(frame)
local icon = trim(a.icon or "") ~= "" and a.icon or "Nada.png"
local pve = trim(a.powerpve or "")
local pvp = trim(a.powerpvp or "")
local cd = trim(a.cooldown or "")
local video = trim(a.video or "")
local energy = trim(a.energy or "")
-- Formato: icon~pve~pvp~cd~video~energy
return icon .. "~" .. pve .. "~" .. pvp .. "~" .. cd .. "~" .. video .. "~" .. energy
end
-- Helper para parsear formato de weapon (icon~pve~pvp~cd~video~energy)
local function parseWeaponString(str)
if not str or trim(str) == "" then return nil end
str = trim(str)
-- Se começa com {, é JSON (formato antigo)
if str:sub(1, 1) == "{" then
local ok, data = pcall(mw.text.jsonDecode, str)
if ok and type(data) == "table" then
local result = {
icon = data.icon or "Nada.png"
}
-- Só inclui campos que existem (não inclui nil)
if data.powerpve ~= nil then result.powerpve = data.powerpve end
if data.powerpvp ~= nil then result.powerpvp = data.powerpvp end
if data.cooldown ~= nil then result.cooldown = data.cooldown end
if data.energy ~= nil then result.energy = data.energy end
if data.video and trim(data.video) ~= "" then result.video = data.video end
return result
end
return nil
end
-- Formato novo: icon~pve~pvp~cd~video~energy
-- IMPORTANTE: precisa preservar campos vazios para manter ordem fixa
local parts = {}
-- Split manual que preserva campos vazios
local start = 1
while true do
local pos = str:find("~", start)
if not pos then
-- Último campo
table.insert(parts, trim(str:sub(start)))
break
end
table.insert(parts, trim(str:sub(start, pos - 1)))
start = pos + 1
end
-- Garante que temos pelo menos 6 campos (preenche com "" se necessário)
while #parts < 6 do
table.insert(parts, "")
end
-- Também aceita formato com vírgula: pve,pvp,cd,video
if #parts < 2 then
parts = {}
for part in str:gmatch("[^,]+") do
table.insert(parts, trim(part))
end
if #parts >= 1 then
local result = {
icon = "Nada.png"
}
-- Só inclui campos que existem (não inclui nil)
if nz(parts[1]) then result.powerpve = parts[1] end
if nz(parts[2]) then result.powerpvp = parts[2] end
if nz(parts[3]) then result.cooldown = parts[3] end
if parts[4] and trim(parts[4]) ~= "" then result.video = parts[4] end
if nz(parts[5]) then result.energy = parts[5] end
return result
end
return nil
end
-- Garante que temos pelo menos as partes básicas (icon sempre existe)
local result = {
icon = nz(parts[1]) or "Nada.png"
}
-- Só inclui campos que existem (não inclui nil)
if nz(parts[2]) then result.powerpve = parts[2] end
if nz(parts[3]) then result.powerpvp = parts[3] end
if nz(parts[4]) then result.cooldown = parts[4] end
if parts[5] and trim(parts[5]) ~= "" then result.video = parts[5] end
if nz(parts[6]) then result.energy = parts[6] end
return result
end
local function assignWeaponFromArg(obj, weaponStr)
if not obj then return end
local packed = trim(weaponStr or "")
if packed ~= "" then
-- Remove quebras de linha e espaços extras que podem vir da expansão do MediaWiki
packed = packed:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "")
obj.weaponPacked = packed
local parsed = parseWeaponString(packed)
if parsed then
obj.weapon = parsed
end
end
end
-- Normaliza uma tabela weapon vinda do módulo do personagem
-- Aceita tanto weapon.desc quanto weapon.desc_i18n e sempre retorna weapon.desc_i18n
local function normalizeWeaponTable(raw)
if type(raw) ~= "table" then
return nil
end
local w = {}
-- Só inclui campos que existem (não inclui nil)
if raw.icon then w.icon = raw.icon else w.icon = "Nada.png" end
if raw.powerpve ~= nil then w.powerpve = raw.powerpve end
if raw.powerpvp ~= nil then w.powerpvp = raw.powerpvp end
if raw.cooldown ~= nil then w.cooldown = raw.cooldown end
if raw.energy ~= nil then w.energy = raw.energy end
-- Só inclui video se não estiver vazio
if raw.video and trim(raw.video) ~= "" then
w.video = raw.video
end
-- Aceita tanto raw.desc_i18n quanto raw.desc
local src = nil
if type(raw.desc_i18n) == "table" then
src = raw.desc_i18n
elseif type(raw.desc) == "table" then
src = raw.desc
end
if src then
local desc_i18n = {}
for _, code in ipairs({ "pt", "en", "es", "pl" }) do
local d = src[code]
if d and trim(d) ~= "" then
-- Usa a função colorize já existente no módulo
desc_i18n[code] = colorize(d)
end
end
if next(desc_i18n) ~= nil then
w.desc_i18n = desc_i18n
end
end
return w
end
local function ensureWeaponHydrated(sub)
if type(sub) ~= "table" then return end
-- Se weapon já é uma tabela válida, normaliza para garantir desc_i18n
if sub.weapon and type(sub.weapon) == "table" and next(sub.weapon) ~= nil then
local normalized = normalizeWeaponTable(sub.weapon)
if normalized then
sub.weapon = normalized
end
return -- Já está hidratado e normalizado
end
-- Se weapon é string, tenta parsear
if sub.weapon and type(sub.weapon) ~= "table" then
local parsed = parseWeaponString(sub.weapon)
if parsed and next(parsed) ~= nil then
sub.weapon = parsed
else
-- Se parse falhou mas tem weaponPacked, tenta depois
if not sub.weaponPacked then
sub.weaponPacked = sub.weapon
end
sub.weapon = nil
end
end
-- Se não tem weapon mas tem weaponPacked, tenta parsear
if not sub.weapon and sub.weaponPacked and trim(sub.weaponPacked) ~= "" then
local parsed = parseWeaponString(sub.weaponPacked)
if parsed and next(parsed) ~= nil then
sub.weapon = parsed
end
end
end
local function normalizeSubTree(sub)
if type(sub) ~= "table" then return end
-- Preserva weapon antes de hidratar
local hadWeapon = sub.weapon ~= nil
local weaponBefore = sub.weapon
ensureWeaponHydrated(sub)
-- Se tinha weapon mas perdeu após hidratação, tenta recuperar do weaponPacked
if hadWeapon and not sub.weapon and sub.weaponPacked then
local parsed = parseWeaponString(sub.weaponPacked)
if parsed and next(parsed) ~= nil then
sub.weapon = parsed
end
end
-- Se tinha weapon como objeto válido antes, preserva mesmo se ensureWeaponHydrated removeu
if weaponBefore and type(weaponBefore) == "table" and next(weaponBefore) ~= nil and not sub.weapon then
sub.weapon = weaponBefore
end
if type(sub.subs) == "table" then
for _, nested in ipairs(sub.subs) do
normalizeSubTree(nested)
end
end
-- Só remove weaponPacked se weapon está válido
if sub.weapon and type(sub.weapon) == "table" and next(sub.weapon) ~= nil then
sub.weaponPacked = nil
end
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)
end
-- Subskills vindas via |subs=
do
local subsPacked = a.subs or ""
local subsArr = {}
-- Primeiro: extrai índices numéricos do início (antes dos JSONs)
-- Formato: "1,2,3,4,5 {json1}{json2}" ou só "1,2,3,4,5"
local indexPart = subsPacked:match("^([%s%d,]+)")
local addedIndices = {} -- Track which indices were added
if indexPart then
for idx in indexPart:gmatch("(%d+)") do
local i = tonumber(idx)
if i and data and data.order and data.order[i] then
local skillName = data.order[i]
local skillData = (data.skills or {})[skillName] or {}
local subObj = {
refS = tostring(i),
name = skillName,
n = skillName,
icon = "", -- JS vai herdar da skill principal
level = "",
video = "",
energy = nil,
powerpve = nil,
powerpvp = nil,
cooldown = nil
}
-- Busca descrição do módulo
if type(skillData.desc) == "table" then
local desc_i18n = {}
local langs = { "pt", "en", "es", "pl" }
for _, code in ipairs(langs) do
local d = skillData.desc[code]
if d and trim(d) ~= "" then
desc_i18n[code] = colorize(d)
end
end
if next(desc_i18n) then
subObj.desc_i18n = desc_i18n
subObj.descPt = desc_i18n.pt
subObj.descEn = desc_i18n.en
subObj.descEs = desc_i18n.es
subObj.descPl = desc_i18n.pl
end
end
table.insert(subsArr, subObj)
addedIndices[tostring(i)] = #subsArr -- Store position
end
end
end
-- Depois: processa JSONs (das {{Subskill}} expandidas)
local chunks = collectJsonObjects(subsPacked)
-- Se um JSON tem refM que já foi adicionado via índice, substitui/merge
for _, chunk in ipairs(chunks) do
local ok, sk = pcall(mw.text.jsonDecode, chunk)
if ok and type(sk) == "table" then
ensureWeaponHydrated(sk)
local ref = sk.refM or sk.M or sk.m or sk.refS or sk.S or sk.s
if ref and addedIndices[tostring(ref)] then
-- Merge: atualiza o objeto existente com os atributos do JSON
local pos = addedIndices[tostring(ref)]
local existing = subsArr[pos]
-- Sobrescreve apenas atributos não vazios do JSON
if sk.video and trim(sk.video) ~= "" then existing.video = sk.video end
if sk.icon and trim(sk.icon) ~= "" then existing.icon = sk.icon end
if sk.level and trim(tostring(sk.level)) ~= "" then existing.level = sk.level end
if sk.energy ~= nil then existing.energy = sk.energy end
if sk.powerpve ~= nil then existing.powerpve = sk.powerpve end
if sk.powerpvp ~= nil then existing.powerpvp = sk.powerpvp end
if sk.cooldown ~= nil then existing.cooldown = sk.cooldown end
if sk.weapon ~= nil then existing.weapon = sk.weapon end
if sk.weaponPacked ~= nil then existing.weaponPacked = sk.weaponPacked end
-- BUSCA WEAPON DO MÓDULO LUA para fazer merge
-- Se weapon já existe mas não tem desc_i18n, busca do módulo para completar
local subName = existing.name or existing.n or ""
if subName ~= "" then
for _, skillKey in ipairs(data.order or {}) do
local skillData = (data.skills or {})[skillKey]
if type(skillData) == "table" and type(skillData.subskills) == "table" then
local moduleSub = skillData.subskills[subName]
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
local moduleWeapon = normalizeWeaponTable(moduleSub.weapon)
if moduleWeapon then
if existing.weapon and type(existing.weapon) == "table" then
-- Merge: preserva atributos do template, adiciona desc_i18n do módulo
if moduleWeapon.desc_i18n and (not existing.weapon.desc_i18n) then
existing.weapon.desc_i18n = moduleWeapon.desc_i18n
end
-- Se não tem icon no template, usa do módulo
if (not existing.weapon.icon or existing.weapon.icon == "Nada.png") and moduleWeapon.icon then
existing.weapon.icon = moduleWeapon.icon
end
-- Se não tem atributos no template, usa do módulo
if existing.weapon.powerpve == nil and moduleWeapon.powerpve ~= nil then
existing.weapon.powerpve = moduleWeapon.powerpve
end
if existing.weapon.powerpvp == nil and moduleWeapon.powerpvp ~= nil then
existing.weapon.powerpvp = moduleWeapon.powerpvp
end
if existing.weapon.cooldown == nil and moduleWeapon.cooldown ~= nil then
existing.weapon.cooldown = moduleWeapon.cooldown
end
if existing.weapon.energy == nil and moduleWeapon.energy ~= nil then
existing.weapon.energy = moduleWeapon.energy
end
if (not existing.weapon.video or existing.weapon.video == "") and moduleWeapon.video then
existing.weapon.video = moduleWeapon.video
end
else
-- Se não tem weapon ainda, usa o do módulo
existing.weapon = moduleWeapon
end
break
end
end
end
end
end
-- Garante que weapon está hidratado após merge
ensureWeaponHydrated(existing)
-- Marca como já processado para não adicionar de novo
sk._merged = true
end
end
end
for _, chunk in ipairs(chunks) do
local ok, sk = pcall(mw.text.jsonDecode, chunk)
if ok and type(sk) == "table" then
ensureWeaponHydrated(sk)
-- Pula se já foi mergeado com um índice
local ref = sk.refM or sk.M or sk.m or sk.refS or sk.S or sk.s
local skipChunk = false
if ref and addedIndices[tostring(ref)] then
-- Já foi processado no merge acima, pula
skipChunk = true
end
if not skipChunk then
-- Se tem ref (M ou S), aplica herança do cache OU busca diretamente
if ref and trim(tostring(ref)) ~= "" then
local mainMeta = fetchSkillMeta(char, ref)
-- Se não tem no cache, ref corresponde a M=N, então busca pelo índice
if not mainMeta then
local idx = tonumber(ref)
if idx and data and data.order and data.order[idx] then
local skillName = data.order[idx]
local skillData = (data.skills or {})[skillName] or {}
-- Cria um meta básico com dados do módulo
mainMeta = {
name = skillName,
icon = "", -- será preenchido pelo JS
level = "",
video = "",
energy = nil,
powerpve = nil,
powerpvp = nil,
cooldown = nil
}
-- Busca descrição
if type(skillData.desc) == "table" then
local langs = { "pt", "en", "es", "pl" }
for _, code in ipairs(langs) do
local d = skillData.desc[code]
if d and trim(d) ~= "" then
mainMeta["desc" .. code:sub(1, 1):upper() .. code:sub(2)] = colorize(d)
end
end
end
end
end
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
-- Busca descrição do módulo pelo nome
local foundDesc = resolveDescWithInheritance(data, mainMeta.name, 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
sk["desc" .. code:sub(1, 1):upper() .. code:sub(2)] = colorize(d)
end
end
sk.desc_i18n = {
pt = sk.descPt,
en = sk.descEn,
es = sk.descEs,
pl = sk.descPl
}
elseif mainMeta.descPt or mainMeta.descEn 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
-- Mesmo sem mainMeta, mantém refM para o JS processar
if not mainMeta then
sk.refM = ref
end
end
-- Fallback: Se tem refM explícito no JSON, aplica herança do cache
local refMExplicit = sk.refM or sk.M or sk.m
if refMExplicit and trim(tostring(refMExplicit)) ~= "" and not ref then
local mainMeta = fetchSkillMeta(char, refMExplicit)
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
-- BUSCA WEAPON DO MÓDULO LUA para fazer merge
-- Se weapon já existe mas não tem desc_i18n, busca do módulo para completar
local subName = sk.name or sk.n or ""
if subName ~= "" then
for _, skillKey in ipairs(data.order or {}) do
local skillData = (data.skills or {})[skillKey]
if type(skillData) == "table" and type(skillData.subskills) == "table" then
local moduleSub = skillData.subskills[subName]
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
local moduleWeapon = normalizeWeaponTable(moduleSub.weapon)
if moduleWeapon then
if sk.weapon and type(sk.weapon) == "table" then
-- Merge: preserva atributos do template, adiciona desc_i18n do módulo
if moduleWeapon.desc_i18n and (not sk.weapon.desc_i18n) then
sk.weapon.desc_i18n = moduleWeapon.desc_i18n
end
-- Se não tem icon no template, usa do módulo
if (not sk.weapon.icon or sk.weapon.icon == "Nada.png") and moduleWeapon.icon then
sk.weapon.icon = moduleWeapon.icon
end
-- Se não tem atributos no template, usa do módulo
if sk.weapon.powerpve == nil and moduleWeapon.powerpve ~= nil then
sk.weapon.powerpve = moduleWeapon.powerpve
end
if sk.weapon.powerpvp == nil and moduleWeapon.powerpvp ~= nil then
sk.weapon.powerpvp = moduleWeapon.powerpvp
end
if sk.weapon.cooldown == nil and moduleWeapon.cooldown ~= nil then
sk.weapon.cooldown = moduleWeapon.cooldown
end
if sk.weapon.energy == nil and moduleWeapon.energy ~= nil then
sk.weapon.energy = moduleWeapon.energy
end
if (not sk.weapon.video or sk.weapon.video == "") and moduleWeapon.video then
sk.weapon.video = moduleWeapon.video
end
else
-- Se não tem weapon ainda, usa o do módulo
sk.weapon = moduleWeapon
end
break
end
end
end
end
end
-- Garante que weapon está normalizado antes de adicionar
ensureWeaponHydrated(sk)
table.insert(subsArr, sk)
end
end -- end if not skipChunk
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
}
-- INCLUI WEAPON do módulo Lua se existir
if type(sub.weapon) == "table" then
local weaponObj = normalizeWeaponTable(sub.weapon)
if weaponObj then
subObj.weapon = weaponObj
end
end
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)
local nestedObj = {
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
}
-- INCLUI WEAPON do nested se existir
if type(s2.weapon) == "table" then
local weaponObj = normalizeWeaponTable(s2.weapon)
if weaponObj then
nestedObj.weapon = weaponObj
end
end
table.insert(nested, nestedObj)
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)
-- BUSCA WEAPON DO MÓDULO LUA se não tem weapon ainda (após herança)
local hasWeapon = subsArr[i].weapon and (
(type(subsArr[i].weapon) == "table" and next(subsArr[i].weapon) ~= nil) or
(type(subsArr[i].weapon) == "string" and trim(subsArr[i].weapon) ~= "")
)
if not hasWeapon then
local subName = subsArr[i].name or subsArr[i].n or ""
if subName ~= "" then
for _, skillKey in ipairs(data.order or {}) do
local skillData = (data.skills or {})[skillKey]
if type(skillData) == "table" and type(skillData.subskills) == "table" then
local moduleSub = skillData.subskills[subName]
if moduleSub then
-- DEBUG: Log temporário
if subName == "Karmic Jishin" then
-- Verifica se tem weapon
end
end
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
local weaponObj = {
icon = moduleSub.weapon.icon or "Nada.png",
energy = moduleSub.weapon.energy,
powerpve = moduleSub.weapon.powerpve,
powerpvp = moduleSub.weapon.powerpvp,
cooldown = moduleSub.weapon.cooldown,
video = moduleSub.weapon.video or ""
}
if type(moduleSub.weapon.desc) == "table" then
local weaponDesc = {}
for _, code in ipairs({ "pt", "en", "es", "pl" }) do
local d = moduleSub.weapon.desc[code]
if d and trim(d) ~= "" then
weaponDesc[code] = colorize(d)
end
end
if next(weaponDesc) then
weaponObj.desc_i18n = weaponDesc
end
end
subsArr[i].weapon = weaponObj
-- DEBUG: Log para confirmar que encontrou
if subName == "Karmic Jishin" then
-- Log temporário
end
break
end
end
end
end
end
normalizeSubTree(subsArr[i])
-- VERIFICAÇÃO FINAL: Garante que weapon está presente e completo antes de serializar
-- Busca do módulo para fazer merge (mesmo se já tem weapon do template)
local subName = subsArr[i].name or subsArr[i].n or ""
if subName ~= "" then
for _, skillKey in ipairs(data.order or {}) do
local skillData = (data.skills or {})[skillKey]
if type(skillData) == "table" and type(skillData.subskills) == "table" then
local moduleSub = skillData.subskills[subName]
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
local moduleWeapon = normalizeWeaponTable(moduleSub.weapon)
if moduleWeapon then
if subsArr[i].weapon and type(subsArr[i].weapon) == "table" then
-- Merge: preserva atributos do template, adiciona desc_i18n do módulo
if moduleWeapon.desc_i18n and (not subsArr[i].weapon.desc_i18n) then
subsArr[i].weapon.desc_i18n = moduleWeapon.desc_i18n
end
-- Se não tem icon no template, usa do módulo
if (not subsArr[i].weapon.icon or subsArr[i].weapon.icon == "Nada.png") and moduleWeapon.icon then
subsArr[i].weapon.icon = moduleWeapon.icon
end
-- Se não tem atributos no template, usa do módulo
if subsArr[i].weapon.powerpve == nil and moduleWeapon.powerpve ~= nil then
subsArr[i].weapon.powerpve = moduleWeapon.powerpve
end
if subsArr[i].weapon.powerpvp == nil and moduleWeapon.powerpvp ~= nil then
subsArr[i].weapon.powerpvp = moduleWeapon.powerpvp
end
if subsArr[i].weapon.cooldown == nil and moduleWeapon.cooldown ~= nil then
subsArr[i].weapon.cooldown = moduleWeapon.cooldown
end
if subsArr[i].weapon.energy == nil and moduleWeapon.energy ~= nil then
subsArr[i].weapon.energy = moduleWeapon.energy
end
if (not subsArr[i].weapon.video or subsArr[i].weapon.video == "") and moduleWeapon.video then
subsArr[i].weapon.video = moduleWeapon.video
end
else
-- Se não tem weapon ainda, usa o do módulo
subsArr[i].weapon = moduleWeapon
end
break
end
end
end
end
end
end
obj.subs = subsArr
-- SISTEMA UNIFICADO DE WEAPON PARA SUBSKILLS: Garante weapon normalizado e completo antes de serializar
if type(obj.subs) == "table" then
for i, sub in ipairs(obj.subs) do
local subName = sub.name or sub.n or ""
-- PASSO 1: Normaliza weapon existente
ensureWeaponHydrated(sub)
normalizeSubTree(sub)
-- PASSO 2: Se weapon ainda é string, tenta parsear
if sub.weapon and type(sub.weapon) ~= "table" then
local parsed = parseWeaponString(sub.weapon)
if parsed and next(parsed) ~= nil then
sub.weapon = parsed
else
sub.weapon = nil
end
end
-- PASSO 3: Busca weapon do módulo (sempre, para garantir merge completo)
if subName ~= "" then
for _, skillKey in ipairs(data.order or {}) do
local skillData = (data.skills or {})[skillKey]
if type(skillData) == "table" and type(skillData.subskills) == "table" then
-- Tenta match exato primeiro
local moduleSub = skillData.subskills[subName]
-- Se não encontrou, tenta case-insensitive
if not moduleSub then
for k, v in pairs(skillData.subskills) do
if type(k) == "string" and k:lower() == subName:lower() then
moduleSub = v
break
end
end
end
-- Se encontrou subskill no módulo com weapon, faz merge completo
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
local moduleWeapon = normalizeWeaponTable(moduleSub.weapon)
if moduleWeapon then
if obj.subs[i].weapon and type(obj.subs[i].weapon) == "table" then
-- MERGE: Combina weapon do template com weapon do módulo
-- Descrição do módulo sempre tem prioridade (tem desc_i18n completo)
if moduleWeapon.desc_i18n then
obj.subs[i].weapon.desc_i18n = moduleWeapon.desc_i18n
end
-- Atributos: usa do módulo se não tem no template
if not obj.subs[i].weapon.icon or obj.subs[i].weapon.icon == "Nada.png" then
obj.subs[i].weapon.icon = moduleWeapon.icon
end
if obj.subs[i].weapon.powerpve == nil then
obj.subs[i].weapon.powerpve = moduleWeapon.powerpve
end
if obj.subs[i].weapon.powerpvp == nil then
obj.subs[i].weapon.powerpvp = moduleWeapon.powerpvp
end
if obj.subs[i].weapon.cooldown == nil then
obj.subs[i].weapon.cooldown = moduleWeapon.cooldown
end
if obj.subs[i].weapon.energy == nil then
obj.subs[i].weapon.energy = moduleWeapon.energy
end
if not obj.subs[i].weapon.video or obj.subs[i].weapon.video == "" then
obj.subs[i].weapon.video = moduleWeapon.video
end
else
-- Se não tem weapon ainda, usa o do módulo completo
obj.subs[i].weapon = moduleWeapon
end
break
end
end
end
end
end
-- PASSO 4: Garante que weapon está normalizado e válido
if obj.subs[i].weapon then
ensureWeaponHydrated(obj.subs[i])
-- Remove weaponPacked se weapon está válido
if type(obj.subs[i].weapon) == "table" and next(obj.subs[i].weapon) ~= nil then
obj.subs[i].weaponPacked = nil
-- Garante que desc_i18n existe se tem descrição
if obj.subs[i].weapon.desc_i18n and type(obj.subs[i].weapon.desc_i18n) == "table" then
-- Já está normalizado
elseif obj.subs[i].weapon.desc and type(obj.subs[i].weapon.desc) == "table" then
-- Converte desc para desc_i18n
local desc_i18n = {}
for _, code in ipairs({ "pt", "en", "es", "pl" }) do
local d = obj.subs[i].weapon.desc[code]
if d and trim(d) ~= "" then
desc_i18n[code] = colorize(d)
end
end
if next(desc_i18n) ~= nil then
obj.subs[i].weapon.desc_i18n = desc_i18n
end
end
else
obj.subs[i].weapon = nil
end
end
end
end
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
assignWeaponFromArg(obj, a.weapon)
if obj.weapon then
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" then
local normalized = normalizeWeaponTable(sk.weapon)
if normalized then
-- Preserva campos já existentes em obj.weapon se houver
if obj.weapon then
-- Merge inteligente: só sobrescreve se o campo do módulo não estiver vazio
-- E só sobrescreve se o campo do template estiver vazio ou não existir
for k, v in pairs(normalized) do
-- Para video: SEMPRE preserva o do template se existir (não faz merge)
if k == "video" then
-- Se o template tem vídeo, preserva (não sobrescreve com módulo)
if obj.weapon.video and trim(obj.weapon.video) ~= "" then
-- Mantém o vídeo do template, não faz nada
elseif v and v ~= "" then
-- Só usa do módulo se template não tem
obj.weapon.video = v
end
-- Para icon: preserva o do template se não for Nada.png
elseif k == "icon" then
if (not obj.weapon.icon or obj.weapon.icon == "" or obj.weapon.icon == "Nada.png") and v and v ~= "" and v ~= "Nada.png" then
obj.weapon.icon = v
end
-- Para outros campos: só faz merge se o campo NÃO EXISTE no template (nil)
-- Se o campo existe no template (mesmo que vazio), preserva o do template
else
-- IMPORTANTE: só faz merge se o campo é nil (não existe), não se está vazio
if obj.weapon[k] == nil and v ~= nil and v ~= "" then
obj.weapon[k] = v
end
end
end
-- Sempre adiciona desc_i18n do módulo se não tiver no template
if normalized.desc_i18n and not obj.weapon.desc_i18n then
obj.weapon.desc_i18n = normalized.desc_i18n
end
else
obj.weapon = normalized
end
-- Adiciona campos descPt, descEn, etc. para compatibilidade
if obj.weapon.desc_i18n then
obj.weapon.descPt = obj.weapon.desc_i18n.pt
obj.weapon.descEn = obj.weapon.desc_i18n.en
obj.weapon.descEs = obj.weapon.desc_i18n.es
obj.weapon.descPl = obj.weapon.desc_i18n.pl
end
end
end
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 "")
-- Prioriza |M= sobre |S= para herança
local ref = (refM ~= "" and refM) or refS
local char = resolveCharFromFrames(frame, a)
local data = requireCharacterModule(char) or {}
-- Se tem |M= ou |S=, busca do cache e herda atributos
if ref ~= "" then
local meta = fetchSkillMeta(char, ref)
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,
refM = ref
}
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
-- Weapon para subskill
assignWeaponFromArg(obj, a.weapon)
return mw.text.jsonEncode(obj)
else
-- Cache vazio - busca nome e descrição diretamente do módulo
-- ref corresponde ao índice M
local idx = tonumber(ref)
local skillName = ""
local desc_i18n = {}
if idx and data and data.order and data.order[idx] then
skillName = data.order[idx]
local skillData = (data.skills or {})[skillName] or {}
if type(skillData.desc) == "table" then
local langs = { "pt", "en", "es", "pl" }
for _, code in ipairs(langs) do
local d = skillData.desc[code]
if d and trim(d) ~= "" then
desc_i18n[code] = colorize(d)
end
end
end
end
local obj = {
refM = ref,
name = skillName,
n = skillName,
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 trim(a.n or "") ~= "" then
obj.name = trim(a.n)
obj.n = trim(a.n)
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
local flagsArr = parseFlags(a.flags)
if #flagsArr > 0 then
obj.flags = flagsArr
end
-- Weapon para subskill
assignWeaponFromArg(obj, a.weapon)
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
-- Weapon para subskill
assignWeaponFromArg(obj, a.weapon)
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
ensureWeaponHydrated(sub)
table.insert(subsArr, sub)
end
end
if #subsArr > 0 then
for _, sub in ipairs(subsArr) do
-- BUSCA WEAPON DO MÓDULO LUA se não tem weapon ainda (para nested subs também)
local hasWeapon = sub.weapon and (
(type(sub.weapon) == "table" and next(sub.weapon) ~= nil) or
(type(sub.weapon) == "string" and trim(sub.weapon) ~= "")
)
if not hasWeapon then
local subName = sub.name or sub.n or ""
if subName ~= "" then
for _, skillKey in ipairs(data.order or {}) do
local skillData = (data.skills or {})[skillKey]
if type(skillData) == "table" and type(skillData.subskills) == "table" then
local moduleSub = skillData.subskills[subName]
if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
local weaponObj = normalizeWeaponTable(moduleSub.weapon)
if weaponObj then
sub.weapon = weaponObj
break
end
end
end
end
end
end
normalizeSubTree(sub)
end
obj.subs = subsArr
end
end
-- Weapon para subskill
-- Usa {{Weapon|...}} que retorna formato: icon~pve~pvp~cd~video~energy
do
assignWeaponFromArg(obj, a.weapon)
-- Busca descrição do weapon no módulo Lua (subskills)
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.weapon) == "table" then
local normalized = normalizeWeaponTable(sub.weapon)
if normalized then
-- Preserva campos já existentes em obj.weapon se houver
if obj.weapon then
-- Merge inteligente: só sobrescreve se o campo do módulo não estiver vazio
-- E só sobrescreve se o campo do template estiver vazio ou não existir
for k, v in pairs(normalized) do
-- Para video: SEMPRE preserva o do template se existir (não faz merge)
if k == "video" then
-- Se o template tem vídeo, preserva (não sobrescreve com módulo)
if obj.weapon.video and trim(obj.weapon.video) ~= "" then
-- Mantém o vídeo do template, não faz nada
elseif v and v ~= "" then
-- Só usa do módulo se template não tem
obj.weapon.video = v
end
-- Para icon: preserva o do template se não for Nada.png
elseif k == "icon" then
if (not obj.weapon.icon or obj.weapon.icon == "" or obj.weapon.icon == "Nada.png") and v and v ~= "" and v ~= "Nada.png" then
obj.weapon.icon = v
end
-- Para outros campos: só faz merge se o campo NÃO EXISTE no template (nil)
-- Se o campo existe no template (mesmo que vazio), preserva o do template
else
-- IMPORTANTE: só faz merge se o campo é nil (não existe), não se está vazio
if obj.weapon[k] == nil and v ~= nil and v ~= "" then
obj.weapon[k] = v
end
end
end
-- Sempre adiciona desc_i18n do módulo se não tiver no template
if normalized.desc_i18n and not obj.weapon.desc_i18n then
obj.weapon.desc_i18n = normalized.desc_i18n
end
else
obj.weapon = normalized
end
-- Adiciona campos descPt, descEn, etc. para compatibilidade
if obj.weapon.desc_i18n then
obj.weapon.descPt = obj.weapon.desc_i18n.pt
obj.weapon.descEn = obj.weapon.desc_i18n.en
obj.weapon.descEs = obj.weapon.desc_i18n.es
obj.weapon.descPl = obj.weapon.desc_i18n.pl
end
end
break
end
end
end
if obj.weapon and type(obj.weapon) == "table" and next(obj.weapon) == nil then
obj.weapon = nil
end
end
-- GARANTIA FINAL: Normaliza weapon antes de serializar
if obj.weapon then
ensureWeaponHydrated(obj)
-- Se weapon ainda é string ou inválido, tenta parsear
if obj.weapon and type(obj.weapon) ~= "table" then
local parsed = parseWeaponString(obj.weapon)
if parsed and next(parsed) ~= nil then
obj.weapon = parsed
else
obj.weapon = nil
end
end
-- Remove weapon se estiver vazio
if obj.weapon and type(obj.weapon) == "table" and next(obj.weapon) == nil then
obj.weapon = nil
end
end
return mw.text.jsonEncode(obj)
end
return p