Mudanças entre as edições de "Módulo:Conquistas"
Ir para navegação
Ir para pesquisar
(Criou página com 'local p = {} local VALID_TAGS = { geral = true, personagens = true, missao = true, bau = true, navegacao = true, pvp = tr...') |
m |
||
| (7 revisões intermediárias pelo mesmo usuário não estão sendo mostradas) | |||
| Linha 1: | Linha 1: | ||
--[[ | |||
Módulo:Conquistas | |||
Renderiza TODAS as conquistas em uma única passada, lendo o banco | |||
Módulo:AchievementDB (carregado via mw.loadData, cacheado pelo Scribunto). | |||
Usado com Widget:Conquistas via {{#invoke:Conquistas|renderAll}} | |||
(normalmente através da predefinição {{Conquistas}}). | |||
Rewards usam exatamente o mesmo visual do Módulo:Reward (item-wrapper | |||
com tooltip, ordenando berries por último). | |||
]] | |||
local p = {} | local p = {} | ||
local | local Item = require("Módulo:Item") | ||
geral = | -- require (não loadData) porque AchievementDB expõe funções (all/byTab/getById). | ||
personagens = | -- O Scribunto cacheia requires dentro do mesmo parser pass. | ||
missao = | local AchievementDB = require("Módulo:AchievementDB") | ||
bau = | |||
navegacao = | local DEFAULT_LANG = "pt-br" | ||
pvp = | local DEFAULT_ICON = "conquistafoosha.png" | ||
pve = | |||
coliseu = | local TABS = { | ||
poneglyph = | { id = "geral", label = "Geral" }, | ||
indicacao = | { id = "personagens", label = "Personagens" }, | ||
celular = | { id = "missao", label = "Missão" }, | ||
bossrush = | { id = "bau", label = "Baú" }, | ||
{ id = "navegacao", label = "Navegação" }, | |||
{ id = "pvp", label = "PvP" }, | |||
{ id = "pve", label = "PvE" }, | |||
{ id = "coliseu", label = "Coliseu" }, | |||
{ id = "poneglyph", label = "Poneglyph" }, | |||
{ id = "indicacao", label = "Indicação" }, | |||
{ id = "celular", label = "Celular" }, | |||
{ id = "bossrush", label = "Boss Rush" }, | |||
} | } | ||
local VALID_TABS = {} | |||
for _, t in ipairs(TABS) do VALID_TABS[t.id] = true end | |||
local function trim(s) | local function trim(s) | ||
| Linha 20: | Linha 44: | ||
end | end | ||
local function | local function normalizeTab(tag) | ||
local t = trim(tag):lower() | local t = trim(tag):lower() | ||
if t == "" or not | if t == "" or not VALID_TABS[t] then return "geral" end | ||
return t | return t | ||
end | end | ||
local function | local function normalizeIcon(name) | ||
local clean = trim(name) | local clean = trim(name) | ||
if clean == "" then return | if clean == "" then return DEFAULT_ICON end | ||
if clean:match("%.%w+$") then return clean end | if clean:match("%.%w+$") then return clean end | ||
return clean .. ".png" | return clean .. ".png" | ||
end | end | ||
local function | local function formatNumber(num) | ||
local | num = tonumber(num) or 0 | ||
s | if num >= 1e9 then | ||
if | return string.format("%.0f", num / 1e6) .. "KKK" | ||
elseif num >= 1e6 then | |||
return string.format("%.0f", num / 1e6) .. "KK" | |||
elseif num >= 1e4 then | |||
return string.format("%.0f", num / 1e3) .. "K" | |||
else | |||
local s = tostring(num) | |||
return s:reverse():gsub("(%d%d%d)", "%1,"):reverse():gsub("^,", "") | |||
end | |||
end | |||
local function normalizeForMatch(s) | |||
if not s then return "" end | |||
return mw.ustring.lower(mw.text.trim(tostring(s))) | |||
end | |||
local function isBerriesItem(item) | |||
if not item then return false end | |||
local image = normalizeForMatch(item.image) | |||
if image ~= "" and image:find("berr", 1, true) then | |||
return true | |||
end | |||
local namePt = item.names and normalizeForMatch(item.names.pt) or "" | |||
local nameEn = item.names and normalizeForMatch(item.names.en) or "" | |||
if namePt:find("berr", 1, true) or nameEn:find("berr", 1, true) then | |||
return true | |||
end | |||
if item.aliases and type(item.aliases) == "table" then | |||
for _, alias in ipairs(item.aliases) do | |||
local norm = normalizeForMatch(alias) | |||
if norm:find("berr", 1, true) then | |||
return true | |||
end | end | ||
end | end | ||
end | end | ||
return | return item.category == "currency" | ||
end | |||
local function resolveRewardItem(ident) | |||
local item = Item.resolve(ident) | |||
if item then return item end | |||
return { | |||
id = 0, | |||
image = ident:match("%.%w+$") and ident or (ident .. ".png"), | |||
names = { pt = ident, en = ident }, | |||
category = "unknown", | |||
} | |||
end | end | ||
local function | --[[ | ||
local | renderRewards: replica o output do Módulo:Reward (predefinição {{Reward}}) | ||
for _, | para uma lista estruturada [{ ident, qty }, ...]. | ||
- Mesmo wrapper .reward-wrapper | |||
- Mesmo .reward-items | |||
- Mesmo Item.renderOne com tooltip e count | |||
- Berries sempre por último | |||
]] | |||
local function renderRewards(rewards, lang) | |||
if not rewards or #rewards == 0 then return "" end | |||
local wrapper = mw.html.create("div"):addClass("reward-wrapper") | |||
local line = wrapper:tag("div"):addClass("reward-items") | |||
local normais = {} | |||
local berries = {} | |||
for _, r in ipairs(rewards) do | |||
local ident = trim(r.ident) | |||
local qty = tonumber(r.qty) or 0 | |||
if ident ~= "" then | |||
local item = resolveRewardItem(ident) | |||
local isBerries = isBerriesItem(item) | |||
local block = { | |||
item = item, | |||
qty = formatNumber(qty), | |||
} | |||
if isBerries then | |||
table.insert(berries, block) | |||
else | |||
table.insert(normais, block) | |||
end | |||
end | |||
end | end | ||
local function | local function append(b) | ||
line:wikitext(Item.renderOne(b.item, b.qty, lang, { | |||
showTooltip = true, | |||
showCount = true, | |||
})) | |||
end | end | ||
for _, b in ipairs(normais) do append(b) end | |||
for _, b in ipairs(berries) do append(b) end | |||
return tostring(wrapper) | |||
end | |||
--[[ | |||
Constrói o card de uma conquista. Retorna string HTML. | |||
Usa dados puros (não recebe wikitext) — ideal para iteração em massa. | |||
]] | |||
local function renderCard(ach, lang) | |||
local name = "" | |||
if ach.names then | |||
name = trim(ach.names[lang]) ~= "" and ach.names[lang] | |||
or trim(ach.names.pt) ~= "" and ach.names.pt | |||
or trim(ach.names.en) ~= "" and ach.names.en | |||
or "" | |||
end | |||
if name == "" then name = "Titulo da Conquista" end | if name == "" then name = "Titulo da Conquista" end | ||
local desc = "" | |||
if ach.desc then | |||
desc = trim(ach.desc[lang]) ~= "" and ach.desc[lang] | |||
or trim(ach.desc.pt) ~= "" and ach.desc.pt | |||
or trim(ach.desc.en) ~= "" and ach.desc.en | |||
or "" | |||
end | |||
if desc == "" then desc = "Descricao da conquista." end | if desc == "" then desc = "Descricao da conquista." end | ||
local | local icon = normalizeIcon(ach.icon) | ||
local tab = normalizeTab(ach.type) | |||
local hidden = ach.hidden == true | |||
local | local card = mw.html.create("div"):addClass("gla-item"):attr("data-tab", tab) | ||
-- data-id: chave estável pro JS do widget persistir reveals de | |||
-- conquistas secretas no localStorage. Usa ach.id (id numérico do DB) | |||
-- pra ser robusto a mudanças de nome/descrição. | |||
if ach.id ~= nil then card:attr("data-id", tostring(ach.id)) end | |||
if hidden then card:addClass("is-hidden"):attr("data-hidden", "true") end | |||
local subtype = ach.subtype and trim(ach.subtype):lower() or "" | |||
if subtype == "onemany" or subtype == "corrida" then | |||
card:attr("data-subtype", subtype) | |||
end | |||
local main = card:tag("div"):addClass("gla-item-main") | |||
local left = main:tag("div"):addClass("gla-item-left") | |||
-- Ícone sem tamanho explícito: respeita o tamanho do arquivo na wiki | |||
-- (recomendado: 52x52). O CSS no Widget:Conquistas só faz reset visual. | |||
left:tag("div") | left:tag("div") | ||
:addClass("gla-item-icon") | :addClass("gla-item-icon") | ||
:wikitext(string.format("[[File:%s | :wikitext(string.format("[[File:%s|alt=%s|link=]]", icon, name)) | ||
local textWrap = left:tag("div") | local textWrap = left:tag("div") | ||
| Linha 102: | Linha 216: | ||
textWrap:tag("div"):addClass("gla-item-desc"):wikitext(desc) | textWrap:tag("div"):addClass("gla-item-desc"):wikitext(desc) | ||
if ach.rewards and #ach.rewards > 0 then | |||
if # | local rewardHtml = renderRewards(ach.rewards, lang) | ||
local | if rewardHtml ~= "" then | ||
local | local rewardDiv = main:tag("div"):addClass("gla-item-reward") | ||
local labelText = #ach.rewards == 1 and "Recompensa" or "Recompensas" | |||
rewardDiv:tag("div") | |||
:addClass("gla-item-reward-label") | |||
:wikitext(labelText) | |||
rewardDiv:wikitext(rewardHtml) | |||
end | |||
end | |||
card:tag("div") | local info = ach.info and trim(ach.info) or "" | ||
:addClass("gla-item- | local mediaRaw = ach.media and trim(ach.media) or "" | ||
:wikitext(" | local mediaFile = mediaRaw ~= "" and normalizeIcon(mediaRaw) or "" | ||
local hasSpoilerContent = (info ~= "" or mediaFile ~= "") | |||
local spoilerEnabled = ach.spoiler == true or hasSpoilerContent | |||
if spoilerEnabled and hasSpoilerContent then | |||
card:addClass("has-spoiler") | |||
local toggle = main:tag("button") | |||
:addClass("gla-item-spoiler-toggle") | |||
:attr("type", "button") | |||
:attr("aria-expanded", "false") | |||
:attr("title", "Abrir spoiler") | |||
toggle:tag("span"):addClass("gla-item-spoiler-text"):wikitext("Spoiler") | |||
toggle:tag("span"):addClass("gla-item-chevron"):wikitext("›") | |||
local spoiler = card:tag("div"):addClass("gla-item-spoiler") | |||
if info ~= "" then | |||
spoiler:tag("div"):addClass("gla-item-spoiler-info"):wikitext(info) | |||
end | |||
if mediaFile ~= "" then | |||
spoiler:tag("div"):addClass("gla-item-spoiler-media") | |||
:wikitext(string.format("[[File:%s|link=]]", mediaFile)) | |||
end | |||
end | end | ||
| Linha 116: | Linha 256: | ||
end | end | ||
-- | local function getArgs(frame) | ||
p. | local args = frame.args or {} | ||
if not next(args) and frame:getParent() then | |||
args = frame:getParent().args or {} | |||
end | |||
return args | |||
end | |||
-- p.renderAll(frame): emite todos os cards em lista plana (data-tab em cada um). | |||
-- Painéis/header ficam no Widget:Conquistas. | |||
-- | |||
-- O invoke precisa ficar FORA do widget na página: Smarty não reparsa wikitext; | |||
-- links de arquivo (File:/Arquivo:) viram texto cru se o invoke for parâmetro do widget. | |||
-- Na wiki use a predefinição {{Conquistas}} ou, manualmente: | |||
-- {{#widget:Conquistas}} + {{#invoke:Conquistas|renderAll}} | |||
-- | |||
-- O JS do widget move .gla-item[data-tab] para o painel certo e remove .gla-conquistas-source. | |||
-- Parâmetros opcionais: |lang=pt|en , |only= id da aba (debug) | |||
function p.renderAll(frame) | |||
local args = getArgs(frame) | |||
local lang = trim(args.lang) | |||
if lang == "" then lang = DEFAULT_LANG end | |||
local onlyTab = trim(args.only):lower() | |||
local list = AchievementDB.all() or {} | |||
local source = mw.html.create("div"):addClass("gla-conquistas-source") | |||
for _, ach in ipairs(list) do | |||
local tab = normalizeTab(ach.type) | |||
if onlyTab == "" or onlyTab == tab then | |||
source:wikitext(renderCard(ach, lang)) | |||
end | |||
end | |||
return tostring(source) | |||
end | |||
return p | return p | ||
Edição atual tal como às 21h42min de 20 de maio de 2026
A documentação para este módulo pode ser criada em Módulo:Conquistas/doc
--[[
Módulo:Conquistas
Renderiza TODAS as conquistas em uma única passada, lendo o banco
Módulo:AchievementDB (carregado via mw.loadData, cacheado pelo Scribunto).
Usado com Widget:Conquistas via {{#invoke:Conquistas|renderAll}}
(normalmente através da predefinição {{Conquistas}}).
Rewards usam exatamente o mesmo visual do Módulo:Reward (item-wrapper
com tooltip, ordenando berries por último).
]]
local p = {}
local Item = require("Módulo:Item")
-- require (não loadData) porque AchievementDB expõe funções (all/byTab/getById).
-- O Scribunto cacheia requires dentro do mesmo parser pass.
local AchievementDB = require("Módulo:AchievementDB")
local DEFAULT_LANG = "pt-br"
local DEFAULT_ICON = "conquistafoosha.png"
local TABS = {
{ id = "geral", label = "Geral" },
{ id = "personagens", label = "Personagens" },
{ id = "missao", label = "Missão" },
{ id = "bau", label = "Baú" },
{ id = "navegacao", label = "Navegação" },
{ id = "pvp", label = "PvP" },
{ id = "pve", label = "PvE" },
{ id = "coliseu", label = "Coliseu" },
{ id = "poneglyph", label = "Poneglyph" },
{ id = "indicacao", label = "Indicação" },
{ id = "celular", label = "Celular" },
{ id = "bossrush", label = "Boss Rush" },
}
local VALID_TABS = {}
for _, t in ipairs(TABS) do VALID_TABS[t.id] = true end
local function trim(s)
return mw.text.trim(s or "")
end
local function normalizeTab(tag)
local t = trim(tag):lower()
if t == "" or not VALID_TABS[t] then return "geral" end
return t
end
local function normalizeIcon(name)
local clean = trim(name)
if clean == "" then return DEFAULT_ICON end
if clean:match("%.%w+$") then return clean end
return clean .. ".png"
end
local function formatNumber(num)
num = tonumber(num) or 0
if num >= 1e9 then
return string.format("%.0f", num / 1e6) .. "KKK"
elseif num >= 1e6 then
return string.format("%.0f", num / 1e6) .. "KK"
elseif num >= 1e4 then
return string.format("%.0f", num / 1e3) .. "K"
else
local s = tostring(num)
return s:reverse():gsub("(%d%d%d)", "%1,"):reverse():gsub("^,", "")
end
end
local function normalizeForMatch(s)
if not s then return "" end
return mw.ustring.lower(mw.text.trim(tostring(s)))
end
local function isBerriesItem(item)
if not item then return false end
local image = normalizeForMatch(item.image)
if image ~= "" and image:find("berr", 1, true) then
return true
end
local namePt = item.names and normalizeForMatch(item.names.pt) or ""
local nameEn = item.names and normalizeForMatch(item.names.en) or ""
if namePt:find("berr", 1, true) or nameEn:find("berr", 1, true) then
return true
end
if item.aliases and type(item.aliases) == "table" then
for _, alias in ipairs(item.aliases) do
local norm = normalizeForMatch(alias)
if norm:find("berr", 1, true) then
return true
end
end
end
return item.category == "currency"
end
local function resolveRewardItem(ident)
local item = Item.resolve(ident)
if item then return item end
return {
id = 0,
image = ident:match("%.%w+$") and ident or (ident .. ".png"),
names = { pt = ident, en = ident },
category = "unknown",
}
end
--[[
renderRewards: replica o output do Módulo:Reward (predefinição {{Reward}})
para uma lista estruturada [{ ident, qty }, ...].
- Mesmo wrapper .reward-wrapper
- Mesmo .reward-items
- Mesmo Item.renderOne com tooltip e count
- Berries sempre por último
]]
local function renderRewards(rewards, lang)
if not rewards or #rewards == 0 then return "" end
local wrapper = mw.html.create("div"):addClass("reward-wrapper")
local line = wrapper:tag("div"):addClass("reward-items")
local normais = {}
local berries = {}
for _, r in ipairs(rewards) do
local ident = trim(r.ident)
local qty = tonumber(r.qty) or 0
if ident ~= "" then
local item = resolveRewardItem(ident)
local isBerries = isBerriesItem(item)
local block = {
item = item,
qty = formatNumber(qty),
}
if isBerries then
table.insert(berries, block)
else
table.insert(normais, block)
end
end
end
local function append(b)
line:wikitext(Item.renderOne(b.item, b.qty, lang, {
showTooltip = true,
showCount = true,
}))
end
for _, b in ipairs(normais) do append(b) end
for _, b in ipairs(berries) do append(b) end
return tostring(wrapper)
end
--[[
Constrói o card de uma conquista. Retorna string HTML.
Usa dados puros (não recebe wikitext) — ideal para iteração em massa.
]]
local function renderCard(ach, lang)
local name = ""
if ach.names then
name = trim(ach.names[lang]) ~= "" and ach.names[lang]
or trim(ach.names.pt) ~= "" and ach.names.pt
or trim(ach.names.en) ~= "" and ach.names.en
or ""
end
if name == "" then name = "Titulo da Conquista" end
local desc = ""
if ach.desc then
desc = trim(ach.desc[lang]) ~= "" and ach.desc[lang]
or trim(ach.desc.pt) ~= "" and ach.desc.pt
or trim(ach.desc.en) ~= "" and ach.desc.en
or ""
end
if desc == "" then desc = "Descricao da conquista." end
local icon = normalizeIcon(ach.icon)
local tab = normalizeTab(ach.type)
local hidden = ach.hidden == true
local card = mw.html.create("div"):addClass("gla-item"):attr("data-tab", tab)
-- data-id: chave estável pro JS do widget persistir reveals de
-- conquistas secretas no localStorage. Usa ach.id (id numérico do DB)
-- pra ser robusto a mudanças de nome/descrição.
if ach.id ~= nil then card:attr("data-id", tostring(ach.id)) end
if hidden then card:addClass("is-hidden"):attr("data-hidden", "true") end
local subtype = ach.subtype and trim(ach.subtype):lower() or ""
if subtype == "onemany" or subtype == "corrida" then
card:attr("data-subtype", subtype)
end
local main = card:tag("div"):addClass("gla-item-main")
local left = main:tag("div"):addClass("gla-item-left")
-- Ícone sem tamanho explícito: respeita o tamanho do arquivo na wiki
-- (recomendado: 52x52). O CSS no Widget:Conquistas só faz reset visual.
left:tag("div")
:addClass("gla-item-icon")
:wikitext(string.format("[[File:%s|alt=%s|link=]]", icon, name))
local textWrap = left:tag("div")
textWrap:tag("div"):addClass("gla-item-title"):wikitext(name)
textWrap:tag("div"):addClass("gla-item-desc"):wikitext(desc)
if ach.rewards and #ach.rewards > 0 then
local rewardHtml = renderRewards(ach.rewards, lang)
if rewardHtml ~= "" then
local rewardDiv = main:tag("div"):addClass("gla-item-reward")
local labelText = #ach.rewards == 1 and "Recompensa" or "Recompensas"
rewardDiv:tag("div")
:addClass("gla-item-reward-label")
:wikitext(labelText)
rewardDiv:wikitext(rewardHtml)
end
end
local info = ach.info and trim(ach.info) or ""
local mediaRaw = ach.media and trim(ach.media) or ""
local mediaFile = mediaRaw ~= "" and normalizeIcon(mediaRaw) or ""
local hasSpoilerContent = (info ~= "" or mediaFile ~= "")
local spoilerEnabled = ach.spoiler == true or hasSpoilerContent
if spoilerEnabled and hasSpoilerContent then
card:addClass("has-spoiler")
local toggle = main:tag("button")
:addClass("gla-item-spoiler-toggle")
:attr("type", "button")
:attr("aria-expanded", "false")
:attr("title", "Abrir spoiler")
toggle:tag("span"):addClass("gla-item-spoiler-text"):wikitext("Spoiler")
toggle:tag("span"):addClass("gla-item-chevron"):wikitext("›")
local spoiler = card:tag("div"):addClass("gla-item-spoiler")
if info ~= "" then
spoiler:tag("div"):addClass("gla-item-spoiler-info"):wikitext(info)
end
if mediaFile ~= "" then
spoiler:tag("div"):addClass("gla-item-spoiler-media")
:wikitext(string.format("[[File:%s|link=]]", mediaFile))
end
end
return tostring(card)
end
local function getArgs(frame)
local args = frame.args or {}
if not next(args) and frame:getParent() then
args = frame:getParent().args or {}
end
return args
end
-- p.renderAll(frame): emite todos os cards em lista plana (data-tab em cada um).
-- Painéis/header ficam no Widget:Conquistas.
--
-- O invoke precisa ficar FORA do widget na página: Smarty não reparsa wikitext;
-- links de arquivo (File:/Arquivo:) viram texto cru se o invoke for parâmetro do widget.
-- Na wiki use a predefinição {{Conquistas}} ou, manualmente:
-- {{#widget:Conquistas}} + {{#invoke:Conquistas|renderAll}}
--
-- O JS do widget move .gla-item[data-tab] para o painel certo e remove .gla-conquistas-source.
-- Parâmetros opcionais: |lang=pt|en , |only= id da aba (debug)
function p.renderAll(frame)
local args = getArgs(frame)
local lang = trim(args.lang)
if lang == "" then lang = DEFAULT_LANG end
local onlyTab = trim(args.only):lower()
local list = AchievementDB.all() or {}
local source = mw.html.create("div"):addClass("gla-conquistas-source")
for _, ach in ipairs(list) do
local tab = normalizeTab(ach.type)
if onlyTab == "" or onlyTab == tab then
source:wikitext(renderCard(ach, lang))
end
end
return tostring(source)
end
return p