Module:Pokémon distribué/bac à sable
Apparence
La documentation pour ce module peut être créée à Module:Pokémon distribué/bac à sable/doc
--[==[
Module:Pokémon distribué — la fiche d'une distribution.
Rend du wikitexte, repassé par `frame:preprocess`.
Cas d'école : `module-tests.lua`, à lancer avec `lua`.
Le pourquoi de chaque garde, et les défauts déjà payés :
`wiki/README.md`.
Le plan de la fiche. Six colonnes, cinq quand il n'y a pas de ruban.
Chaque ligne du tableau a sa fonction, nommée à droite.
col. 1 col. 2-3 col. 4-5 col. 6
┌─────────┬───────────────┬───────────────────┬─────────┐
│ │ N. 10 │ Capacités │ Ruban │ ligne_entetes
│ ├───────────────┼─────────┬─────────┼─────────┤
│ sprite │ types │ cap. 1 │ type │ ruban │ ligne_types
│ (4 l.) │ Téra/Gigamax │ cap. 2 │ type │ (4 l., │ ligne_tera_gigamax
│ ├──────┬────────┼─────────┼─────────┤ ou 2+2 │
│ │ DO : │ valeur │ cap. 3 │ type │ avec │ ligne_do
├─────────┼──────┼────────┼─────────┼─────────┤ deux) │
│Ball nom │ ID : │ valeur │ cap. 4 │ type │ │ ligne_nom_et_id
├─────────┴──────┴──┬─────┴─────────┴──┬──────┴─────────┤
│ Objet tenu : │ Talent : │ Nature : │ lignes_objet_talent_nature
│ valeur │ valeur │ valeur │
├───────────────────┴──────────────────┴────────────────┤
│ une ligne pleine par donnée présente : origine, │ lignes_du_bas
│ distribution, versions, Carte Cadeau, IV et EV, │
│ rencontre, puis les bandeaux explicatifs │
└───────────────────────────────────────────────────────┘
Sans Téracristal ni Gigamax, la case des types couvre deux lignes.
]==]
local p = {}
-- LES COULEURS DE LA FICHE, reprises de l'ancien modèle en wikitexte.
local FOND_ENTETE = "#ddf"
local FOND_ETIQUETTE = "#eef"
local FOND_VALEUR = "#f4f4fc"
local FOND_CAPACITE = "aliceblue"
local COULEUR_MALE = "DodgerBlue"
local COULEUR_FEMELLE = "red"
-- Les coins arrondis d'une cellule. Une étiquette et sa valeur forment
-- une paire : l'étiquette est arrondie à gauche ou en haut, la valeur à
-- droite ou en bas.
local ARRONDI_A_GAUCHE = "5px 0 0 5px"
local ARRONDI_A_DROITE = "0 5px 5px 0"
local ARRONDI_EN_HAUT = "5px 5px 0 0"
local ARRONDI_EN_BAS = "0 0 5px 5px"
local TIRET = "—"
local NOUVELLE_LIGNE = '\n|- align="center"'
local CATEGORIE_DE_MAINTENANCE =
"[[Catégorie:Page avec un paramètre de distribution incorrect]]"
--- Tous les paramètres que le modèle comprend. Un autre nom est une faute
-- de frappe, ou un reste d'une version ancienne : la page va alors dans
-- la catégorie de maintenance.
local PARAMETRES_CONNUS = {}
for _, nom in ipairs({
"jeu", "num", "taille", "forme", "pokemon", "nom", "nomvarie",
"type", "type2", "type-téra", "gigamax",
"sexe", "sexediff", "chromatique", "baron", "oeuf", "niveau", "nature",
"talent", "talent2", "talent3", "talentcaché",
"do", "genre-do", "traduction-do", "dovarie", "id",
"ball", "objet", "objet2", "objet-transfert", "ruban", "taille-ruban", "ruban2", "taille-ruban2",
"origine", "date", "date-debut", "date-fin", "canal", "versions",
"carte-titre", "carte-numero", "evenement", "region", "pays",
"distribution", "langue",
"iv", "iv-pv", "iv-att", "iv-def", "iv-attspe", "iv-defspe", "iv-vit", "iv-special", "ev",
"lieu-rencontre", "fatidique",
-- Ajoutés le 2026-09-26 (4ᵉ génération) : la rencontre et l'Œuf, les
-- notes du jeu, le jeu d'où vient le Pokémon, la Carte Miracle, la lettre.
"date-rencontre", "niveau-rencontre", "lieu-oeuf", "date-oeuf", "date-selon",
"coreen", "beaute-max", "langue-origine", "oublier", "oublier-depuis", "oublier-par", "ball-varie",
"obtenu-depuis",
"carte-langue", "carte-icones", "carte-message", "carte-titre-fr", "carte-message-fr",
"lettre-message", "lettre-traduction",
-- 5ᵉ génération : la description de la Carte Miracle.
"carte-description", "carte-description-fr",
}) do
PARAMETRES_CONNUS[nom] = true
end
for i = 1, 4 do
for _, prefixe in ipairs({ "cap-nom", "cap-type", "cap-cat", "cap-even" }) do
PARAMETRES_CONNUS[prefixe .. i] = true
end
end
--- Les valeurs admises pour `region`, `pays` et `canal`. Une valeur hors
-- liste range la page dans la catégorie de maintenance : une requête ne
-- retrouve une donnée que si elle est toujours écrite de la même façon.
-- La casse ne compte pas ; la fiche enregistre l'écriture de la liste.
-- ⚠️ Listes de départ, à corriger au fil du chantier.
local function liste(valeurs)
local t = {}
for _, v in ipairs(valeurs) do t[mw.ustring.lower(v)] = v end
return t
end
-- La grande région : elle choisit la section de la page.
local REGIONS = liste({
"Monde", "Japon", "Amérique du Nord", "Amérique latine", "Europe",
"Australie", "Corée du Sud", "Taïwan", "Chine", "Hong Kong", "Asie du Sud-Est",
})
-- Le pays, quand la distribution ne couvrait pas toute la région.
local PAYS = liste({
"Japon", "États-Unis", "Canada", "Mexique", "Brésil",
"Royaume-Uni", "Irlande", "France", "Allemagne", "Espagne", "Italie",
"Belgique", "Pays-Bas", "Luxembourg", "Suisse", "Autriche", "Portugal",
"Danemark", "Norvège", "Suède", "Finlande", "Grèce", "République tchèque",
"Pologne", "Australie", "Nouvelle-Zélande", "Corée du Sud", "Taïwan",
"Chine", "Hong Kong", "Singapour", "Malaisie", "Thaïlande", "Philippines",
"Indonésie", "Porto Rico", "Turquie", "Colombie", "Chili",
"Guatemala", "Venezuela", "Pérou", "Panama", "Costa Rica", "Afrique du Sud", "Russie",
})
-- Le moyen technique de la distribution. Chaque valeur traduit une ou
-- plusieurs écritures de Bulbapedia (bas de fiche `entrybottom`, ou titre
-- de page en 4ᵉ et 5ᵉ génération) :
-- Distribution locale local, local wireless, Local Communication
-- Wi-Fi pages « Wi-Fi … » (4ᵉ et 5ᵉ génération)
-- Nintendo Network Nintendo Network (6ᵉ et 7ᵉ)
-- Internet Internet (8ᵉ et plus)
-- Code de série serial code, Serial Code (A/J/K/U codes)
-- Mot de passe password, Password:
-- Échange Y-Comm trade, Global Room trade
-- Récompense de compétition Battle Stadium Rewards, Official Online Competition
-- « Cadeau Mystère » n'est pas un canal : c'est le menu du jeu qui reçoit
-- presque toutes les distributions.
local CANAUX = liste({
"Distribution locale", "Wi-Fi", "Nintendo Network", "Internet",
"Code de série", "Mot de passe", "Infrarouge", "Nintendo Zone",
"Pokémon Link", "Code QR", "Poké Ball Plus", "GTS", "Échange",
"Récompense de compétition", "Pokémon Global Link", "Pokémon Dream World",
"Pokéwalker", "Pokémon HOME", "En jeu",
})
-- La langue de la liste qui porte la fiche (une par copie, comme le tableau
-- {{eventAvail}} de Bulbapedia sur ses pages de Pokémon).
local LANGUES = liste({
"japonais", "anglais", "français", "allemand", "italien", "espagnol",
"coréen", "chinois",
})
-- OUTILS
--- La valeur d'un paramètre, sans espaces autour. "" quand il est absent.
local function val(args, nom)
local v = args[nom]
if v == nil then return "" end
v = mw.text.trim(v)
-- ⚠️ Un saut de ligne suivi de « | » ou « ! » ouvre une CELLULE ou une
-- LIGNE de tableau. Aucune fiche n'en contient aujourd'hui : la garde
-- est préventive, mais une seule valeur de ce genre casserait la fiche
-- et les fiches suivantes de la page.
return (v:gsub("\n%s*([|!])", " %1"))
end
--- Un paramètre vaut-il `attendu` ? La casse ne compte pas.
local function vaut(args, nom, attendu)
return mw.ustring.lower(val(args, nom)) == mw.ustring.lower(attendu)
end
--- Un paramètre vaut-il « oui » ? La casse ne compte pas.
-- Toute autre valeur vaut « non », y compris `non` : 20 fiches écrivent
-- `nomvarie=non` et ne veulent pas du bandeau.
local function oui(args, nom)
return vaut(args, nom, "oui")
end
--- La valeur, ou un tiret quand elle est vide.
local function ou_tiret(v)
return v ~= "" and v or TIRET
end
--- Joint les morceaux avec `separateur`, en sautant les morceaux vides.
local function joindre_non_vides(morceaux, separateur)
local gardes = {}
for _, m in ipairs(morceaux) do
if m ~= "" then table.insert(gardes, m) end
end
return table.concat(gardes, separateur)
end
--- Les valeurs d'un paramètre à plusieurs valeurs, séparées par des
-- virgules (`pays = Danemark, Norvège, Suède`), ramenées à l'écriture de
-- la liste `admises`. Rend les valeurs reconnues, puis `true` si une
-- valeur est hors liste.
local function valeurs_admises(args, nom, admises)
local reconnues, inconnue = {}, false
for brute in mw.text.gsplit(val(args, nom), ",", true) do
local v = mw.text.trim(brute)
if v ~= "" then
local ecrite = admises[mw.ustring.lower(v)]
if ecrite then
table.insert(reconnues, ecrite)
else
inconnue = true
end
end
end
return reconnues, inconnue
end
--- Le texte sans ses liens : `[[Miaouss Gigamax]]` devient
-- `Miaouss Gigamax`, `[[Pikachu|Pika]]` devient `Pika`.
local function sans_liens(s)
return (s:gsub("%[%[[^%]|]*|([^%]]*)%]%]", "%1"):gsub("%[%[([^%]]*)%]%]", "%1"))
end
--- La première lettre en capitale : `mémoire` devient `Mémoire`.
local function capitale(s)
return mw.ustring.upper(mw.ustring.sub(s, 1, 1)) .. mw.ustring.sub(s, 2)
end
-- LA MISE EN FORME
--- Les coins arrondis, en CSS. `rayon` vaut 5px par défaut.
local function arrondi(rayon)
return "border-radius:" .. (rayon or "5px") .. ";"
end
--- Une cellule du tableau, précédée de son saut de ligne.
-- `c` est une table :
-- c[1] le contenu de la cellule
-- fond la couleur de fond ; sans fond, la cellule n'est pas arrondie
-- arrondi le rayon des coins, 5px par défaut
-- css du CSS en plus, par exemple "font-size:85%;"
-- colspan, rowspan
local function cellule(c)
local attributs = ""
if c.colspan then attributs = attributs .. 'colspan="' .. c.colspan .. '" ' end
if c.rowspan then attributs = attributs .. 'rowspan="' .. c.rowspan .. '" ' end
local css = c.css or ""
if c.fond then
css = "background:" .. c.fond .. "; " .. arrondi(c.arrondi)
.. (css ~= "" and (" " .. css) or "")
end
if css ~= "" then attributs = attributs .. 'style="' .. css .. '" ' end
return "\n| " .. attributs .. (attributs ~= "" and "| " or "") .. c[1]
end
--- Un lien écrit en noir, pour les étiquettes.
local function lien_noir(page, texte)
return "[[" .. page .. '|<span style="color:black">' .. texte .. "</span>]]"
end
--- L'étiquette d'une donnée : petite, grasse, liée à sa page d'aide.
local function etiquette(page, texte)
return "<small>'''" .. lien_noir(page, texte) .. "'''</small>"
end
--- Le texte en gras quand `actif` est vrai.
-- ⚠️ Le gras passe par `<strong>`, pas seulement par le CSS : un lecteur
-- d'écran ignore `font-weight`, et les bandeaux du bas promettent « en
-- gras » à tous les lecteurs.
local function gras(texte, actif)
return actif and ("<strong>" .. texte .. "</strong>") or texte
end
--- La graisse de la cellule, en CSS. Elle double `gras()` pour l'œil.
local function css_gras(actif)
return "font-weight:" .. (actif and "bold" or "normal") .. ";"
end
-- LE SPRITE ET LE NOM
--- Les écritures acceptées pour le sexe, ramenées à m, f ou mf.
-- Les autres valeurs (« Au hasard », `{{?}}`) n'affichent rien : on ne
-- connaît pas toujours le sexe du Pokémon distribué.
local SEXES = {
m = "m", f = "f", mf = "mf",
["♂"] = "m", ["♀"] = "f", ["m/f"] = "mf", ["♂/♀"] = "mf",
}
local function sexe(args)
return SEXES[mw.ustring.lower(val(args, "sexe"))] or ""
end
--- L'image du Pokémon. Le nom du fichier se compose morceau par morceau :
-- Sprite_0025_Alola_♀_chromatique_XY.png
-- numéro, forme, sexe (avec `sexediff`), chromatique, jeu
-- `num` porte parfois un suffixe, comme `0025 ♂` ou `0423 Orient`.
local function sprite(args)
local nom = "Sprite_" .. val(args, "num")
local forme = val(args, "forme")
if forme ~= "" then nom = nom .. "_" .. forme end
-- `sexediff` : l'espèce a une apparence différente selon le sexe.
-- ⚠️ `oui()` et non `~= ""` : `sexediff=non` doit dire NON.
if oui(args, "sexediff") then
nom = nom .. (sexe(args) == "f" and "_♀" or "_♂")
end
if oui(args, "chromatique") then nom = nom .. "_chromatique" end
nom = nom .. "_" .. val(args, "jeu") .. ".png"
local taille = val(args, "taille")
-- ⚠️ Sans `alt`, un lecteur d'écran annonce le nom du fichier.
local espece = val(args, "pokemon")
return "[[Fichier:" .. nom .. (taille ~= "" and ("|" .. taille) or "")
.. (espece ~= "" and ("|alt=" .. espece) or "") .. "]]"
end
local function symbole_sexe(args)
local male = '<span style="color:' .. COULEUR_MALE .. '">♂</span>'
local femelle = '<span style="color:' .. COULEUR_FEMELLE .. '">♀</span>'
local s = sexe(args)
if s == "m" then return " " .. male end
if s == "f" then return " " .. femelle end
if s == "mf" then return " " .. male .. "/" .. femelle end
return ""
end
--- La Ball, le nom du Pokémon, son sexe et ses marques.
-- `pokemon` est l'espèce, et sert de lien. `nom` est le nom porté à la
-- distribution : un nom étranger, ou en capitales avant la 5ᵉ génération.
local function texte_nom(args)
-- 124 fiches écrivent la Ball en minuscules : `capitale()` les accepte.
local ball = val(args, "ball")
if ball == "" then ball = "Poké" end
-- ⚠️ Pokémon Or : les sprites portent le code « O », les miniatures
-- d'objet le code « OA » (Or et Argent). La casse ne compte pas.
local jeu = val(args, "jeu")
if mw.ustring.upper(jeu) == "O" then jeu = "OA" end
local espece = val(args, "pokemon")
local nom = val(args, "nom")
if nom == "" then nom = espece end
local texte = "{{Objet|" .. capitale(ball) .. " Ball|nom=non|jeu=" .. jeu
.. "}} '''[[" .. espece .. "|" .. nom .. "]]'''" .. symbole_sexe(args)
if oui(args, "chromatique") then
texte = texte .. ' [[Pokémon chromatique|<span style="color:'
.. COULEUR_FEMELLE .. '">★</span>]]'
elseif vaut(args, "chromatique", "possible") then
-- Les Œufs mystère de la 2ᵉ génération : le Pokémon PEUT éclore
-- chromatique (Bulbapedia : `shiny=maybe`). Étoile creuse.
-- `1=` et `2=` sont nommés : sans eux, le « = » de `style=` fait de
-- « <span style » un nom de paramètre, et l'infobulle affiche {{{2}}}.
texte = texte .. ' {{Infobulle|1=<span style="color:' .. COULEUR_FEMELLE
.. '">☆</span>|2=Peut être chromatique}}'
end
-- Un Pokémon Baron vient de Légendes Pokémon : Arceus (code LPA).
if oui(args, "baron") then
texte = texte .. " [[Fichier:Icône Pokémon Baron LPA.png|20px|link=Pokémon Baron"
.. "|Pokémon Baron]]"
end
return texte
end
--- Le niveau, et la marque de l'Œuf. Un Œuf a quand même un niveau :
-- 5 ou 1 selon le jeu.
local function texte_niveau(args)
local texte = etiquette("Niveau", "N.") .. " " .. ou_tiret(val(args, "niveau"))
if oui(args, "oeuf") then
texte = texte .. " {{Objet|Œuf|nom=non}}"
.. "{{infobulle|*|Ce Pokémon a été distribué sous forme d'Œuf.}}"
end
return texte
end
-- LES CAPACITÉS
--- La capacité est-elle « au hasard » plutôt qu'un nom ?
-- ⚠️ L'orthographe sans accent est acceptée : sinon, lien rouge.
local function est_aleatoire(nom)
local n = mw.ustring.lower(nom)
return n == "aléatoire" or n == "aleatoire"
end
--- Le type et la catégorie d'une capacité, ou un tiret.
-- `{{type|…}}` dessine aussi la catégorie : il reconnaît `physique`,
-- `spécial` et `statut`. ⚠️ `spéciale`, au féminin, rend un fichier absent.
local function type_de_capacite(args, i)
local t = val(args, "cap-type" .. i)
if t == "" then return TIRET end
local texte = "{{type|" .. t .. "}}"
local cat = val(args, "cap-cat" .. i)
if cat ~= "" then texte = texte .. " {{type|" .. cat .. "}}" end
return texte
end
--- Les deux cellules de la capacité n° i : son nom, puis son type.
-- Une capacité exceptionnelle (`cap-even`) est une capacité que l'espèce
-- n'apprend pas normalement, comme un Pikachu qui connaît Surf.
-- Elle s'affiche en gras, et un bandeau l'explique en bas de la fiche.
local function cellules_capacite(args, i)
local nom = val(args, "cap-nom" .. i)
if nom == "" then
return cellule({ TIRET, colspan = 2 })
end
local texte, droite
if est_aleatoire(nom) then
texte, droite = "Aléatoire", TIRET
else
texte, droite = "[[" .. nom .. "]]", type_de_capacite(args, i)
end
local exceptionnelle = oui(args, "cap-even" .. i)
-- La hauteur fixe garde les quatre lignes de capacités égales.
return cellule({ gras(texte, exceptionnelle), fond = FOND_CAPACITE,
css = "height:28px; " .. css_gras(exceptionnelle) })
.. "\n| " .. droite
end
-- LE DRESSEUR D'ORIGINE (DO) ET L'IDENTIFIANT (ID)
--- Le DO est-il celui du joueur qui reçoit le Pokémon ? `do=joueur`.
local function do_est_le_joueur(args)
return vaut(args, "do", "joueur")
end
--- La couleur du DO dit le sexe du dresseur, comme dans les jeux.
-- `genre-do` ne sert plus à partir de la 6ᵉ génération.
local function couleur_du_do(args)
local g = mw.ustring.lower(val(args, "genre-do"))
if g == "m" then return COULEUR_MALE end
if g == "f" then return COULEUR_FEMELLE end
return "Black"
end
--- Ce qu'on affiche quand personne ne connaît le DO ou l'ID.
local INCONNU = "{{Infobulle|''Inconnu''|Cette information n'est pas connue.}}"
--- Une valeur dit-elle « on ne sait pas » ? `inconnu` (l'écriture
-- conseillée), mais aussi ce que les sources écrivent : `Unknown`, `???`,
-- `?????`, et `{{?}}`, qui arrive ici déjà rendu (« ?? » dans une balise).
-- ⚠️ Enregistrée telle quelle, « ?? » déclenchait l'avertissement de
-- Semantic MediaWiki sous la fiche (constaté le 2026-09-25).
local function est_inconnu(v)
-- Les parenthèses gardent la seule chaîne : `gsub` rend aussi un compte,
-- que `trim` prendrait pour son jeu de caractères.
local nu = mw.text.trim(((v or ""):gsub("<[^>]*>", "")))
local bas = mw.ustring.lower(nu)
return bas == "inconnu" or bas == "unknown" or (nu ~= "" and nu:match("^[%?%s]+$") ~= nil)
end
--- Les DO d'une distribution, séparés par des virgules : un même Mew
-- porte `EUROPE, SWEDEN, NORWAY, FINLAND, DENMARK` selon le pays.
-- ⚠️ Des fiches écrites à la main séparent le dernier par « ou » :
-- `FLAGSB, FLAGSD, FLAGSA, FLAGSC ou FLAGSE`.
-- Un DO inconnu n'est pas un DO : il n'entre pas dans la liste.
local function les_do(args)
local liste = {}
local texte = val(args, "do"):gsub("%s+ou%s+", ",")
for brut in mw.text.gsplit(texte, ",", true) do
local v = mw.text.trim(brut)
if v ~= "" and not est_inconnu(v) then table.insert(liste, v) end
end
return liste
end
local function texte_do(args)
if do_est_le_joueur(args) then
return "{{Infobulle|''Joueur''|Le DO et l'ID sont ceux du joueur "
.. "ayant obtenu le Pokémon.}}"
end
if est_inconnu(val(args, "do")) then return INCONNU end
-- Plusieurs DO : un par ligne, comme Bulbapedia.
local texte = ou_tiret(table.concat(les_do(args), "<br>"))
-- Un DO en caractères japonais ou coréens porte sa traduction en infobulle.
local traduction = val(args, "traduction-do")
if traduction ~= "" then
texte = "{{infobulle|" .. texte .. "|" .. traduction .. "}}"
end
return '<span style="color:' .. couleur_du_do(args) .. '">' .. texte .. "</span>"
end
local function texte_id(args)
-- ⚠️ Si le DO est le joueur, l'ID l'est aussi : on ne l'affiche pas.
if do_est_le_joueur(args) then return TIRET end
if est_inconnu(val(args, "id")) then return INCONNU end
return ou_tiret(val(args, "id"))
end
-- L'OBJET ET LE TALENT
--- Un objet en lien. « - » veut dire « aucun objet », et reste « - ».
local function lien_objet(nom)
return (nom == "-") and "-" or ("[[" .. nom .. "]]")
end
--- Les codes de sprite de la 1ʳᵉ génération : seuls jeux où un objet
-- n'existe qu'au transfert.
local JEUX_DE_LA_PREMIERE_GENERATION = { rv = true, rb = true, j = true, b = true, r = true, v = true }
--- L'objet n'apparaît-il qu'au transfert vers la 2ᵉ génération ?
-- La 1ʳᵉ génération n'a pas d'objet tenu. Mais l'octet du taux de capture
-- devient l'objet tenu quand Or, Argent ou Cristal reçoivent le Pokémon par
-- la Capsule Temporelle, et la machine de distribution (ou Pokémon Stadium)
-- y écrivait une valeur choisie : le Coffre Normal ou le Coffre Luxe de
-- Stadium, la Baie du Magicarpe de Tamamushi. Cet objet fait donc partie de
-- la distribution. Aucun autre passage de génération ne fonctionne ainsi.
local function objet_au_transfert(args)
return oui(args, "objet-transfert")
end
local function est_de_la_premiere_generation(args)
return JEUX_DE_LA_PREMIERE_GENERATION[mw.ustring.lower(val(args, "jeu"))] == true
end
local function texte_objet(args)
-- ⚠️ `objet2` n'est PAS un second objet tenu : c'est l'objet
-- ALTERNATIF, quand il dépendait de la version ou de la vague.
local texte = lien_objet(val(args, "objet"))
local objet2 = val(args, "objet2")
if objet2 ~= "" then
texte = texte .. " <small>ou</small> " .. lien_objet(objet2)
end
if objet_au_transfert(args) then
texte = texte .. "{{Infobulle|<sup>*</sup>|Seulement après un transfert "
.. "vers la deuxième génération, par la Capsule Temporelle.}}"
end
return texte
end
--- Les talents possibles, séparés par « ou ».
-- « - » veut dire « pas de talent » : les talents n'existent pas en
-- 1ʳᵉ et 2ᵉ génération.
--- Les codes de sprite des deux premières générations : ni talent ni
-- nature n'y existent (ils apparaissent en 3ᵉ génération).
local JEUX_SANS_TALENT_NI_NATURE = {
rv = true, rb = true, j = true, b = true, r = true, v = true,
o = true, oa = true, a = true, c = true, oac = true,
}
local AVANT_LA_TROISIEME = "{{Infobulle|—|N'existe pas avant la troisième génération.}}"
--- La fiche vient-elle d'un jeu sans talent ni nature ? Alors « — », et
-- jamais « Au hasard » : la nature n'existait pas (décidé le 2026-09-25).
local function sans_talent_ni_nature(args)
return JEUX_SANS_TALENT_NI_NATURE[mw.ustring.lower(val(args, "jeu"))] == true
end
local function texte_talent(args)
if sans_talent_ni_nature(args) then return AVANT_LA_TROISIEME end
local talents = {}
for _, champ in ipairs({ "talent", "talent2", "talent3" }) do
local v = val(args, champ)
if v == "-" then
table.insert(talents, TIRET)
elseif v ~= "" then
table.insert(talents, "[[" .. v .. "]]")
end
end
if #talents == 0 then return TIRET end
return table.concat(talents, " <small>ou</small> ")
end
-- LE RUBAN
--- Un champ de ruban porte-t-il vraiment un ruban ?
-- ⚠️ « aucun » est une valeur, pas un vide. Écarter les DEUX champs.
local function porte_un_ruban(args, champ)
local r = mw.ustring.lower(val(args, champ))
return r ~= "" and r ~= "aucun"
end
--- La cellule d'un ruban : son image, puis son nom.
-- Le nom du ruban est écrit sous l'image : l'image est donc décorative
-- (`alt=` vide), et un lecteur d'écran ne lit le nom qu'une fois.
local function cellule_ruban(nom, taille, rowspan)
local image = "[[Fichier:Ruban " .. nom .. ".png"
.. (taille ~= "" and ("|" .. taille) or "") .. "|alt=|link=]]"
return cellule({ image .. "<br/>[[Ruban " .. nom .. "]]", rowspan = rowspan,
fond = FOND_CAPACITE, css = "font-size:85%; padding:4px;" })
end
-- LES LIGNES DU TABLEAU, DE HAUT EN BAS
-- Chaque fonction reçoit la fiche `f` (voir `decrire`) et rend sa ligne.
--- L'ancre de la fiche, pour qu'un lien `[[Page#Mew Aura]]` y mène.
-- Elle vient de `evenement`, pas du titre de section : elle survit donc
-- à un titre renommé et à une fiche déplacée d'une page à l'autre.
-- MediaWiki remplace lui-même les espaces par des soulignés, comme pour
-- un titre de section.
local function ancre(f)
if f.evenement == "" then return "" end
return '<span id="' .. f.evenement:gsub('"', """) .. '"></span>'
end
local function ouverture(f)
-- ⚠️ Le cadre qui défile évite le débordement sur écran étroit, et
-- il porte la marge : à l'intérieur, elle mangerait de la course.
-- Le cadre prend la couleur du premier type.
local type1 = val(f.args, "type")
return ancre(f)
.. '<div style="overflow-x:auto; max-width:100%; margin:0 0 0.5em 2em;">'
.. '\n{| style="border:3px double {{Couleur|'
.. (type1 ~= "" and type1 or "inconnu") .. '}}; ' .. arrondi() .. '"'
end
--- Ligne 1 : le sprite (sur 4 lignes), le niveau, les en-têtes.
local function ligne_entetes(f)
local texte = NOUVELLE_LIGNE
.. cellule({ sprite(f.args), rowspan = 4, css = "width:150px;" })
.. cellule({ texte_niveau(f.args), colspan = 2, css = "width:120px;" })
.. cellule({ "'''" .. lien_noir("Capacité", "Capacités") .. "'''", colspan = 2,
fond = FOND_ENTETE, css = "width:200px; height:25px; font-size:90%;" })
if f.a_ruban then
texte = texte .. cellule({ "'''" .. lien_noir("Ruban (série principale)",
"Ruban" .. (f.a_ruban2 and "s" or "")) .. "'''",
fond = FOND_ENTETE, css = "font-size:90%;" })
end
return texte
end
--- Ligne 2 : les types, la capacité 1, le (premier) ruban.
local function ligne_types(f)
local type1, type2 = val(f.args, "type"), val(f.args, "type2")
local texte = NOUVELLE_LIGNE
if f.a_tera or f.a_gigamax then
-- Sur une seule ligne : les deux types côte à côte.
texte = texte .. cellule({ "{{type|" .. type1 .. "}}"
.. (type2 ~= "" and ("{{type|" .. type2 .. "}}") or ""), colspan = 2 })
else
-- Sur deux lignes : `{{type|A|B}}` empile les deux types.
texte = texte .. cellule({ "{{type|" .. type1
.. (type2 ~= "" and ("|" .. type2) or "") .. "}}", colspan = 2, rowspan = 2 })
end
texte = texte .. cellules_capacite(f.args, 1)
if f.a_ruban then
-- Seul, le ruban couvre les lignes 2 à 5. Avec un second ruban,
-- chacun couvre deux lignes.
texte = texte .. cellule_ruban(val(f.args, "ruban"), val(f.args, "taille-ruban"),
f.a_ruban2 and 2 or 4)
end
return texte
end
--- Ligne 3 : le type Téracristal ou le Gigamax, la capacité 2.
-- Sans l'un ni l'autre, la case des types de la ligne 2 descend ici.
local function ligne_tera_gigamax(f)
local texte = NOUVELLE_LIGNE
if f.a_tera then
texte = texte .. cellule({ "<small>Type [[Téracristallisation|Téracristal]] :"
.. "</small> {{type|" .. val(f.args, "type-téra") .. "|téra=oui}}", colspan = 2 })
elseif f.a_gigamax then
texte = texte .. cellule({ "[[Fichier:Miniature Gigamax EB.png|25px|"
.. "link=Gigamax|Capable de gigamaxer]] [[Gigamax]]", colspan = 2 })
end
return texte .. cellules_capacite(f.args, 2)
end
--- Ligne 4 : le DO, la capacité 3, le second ruban.
local function ligne_do(f)
local texte = NOUVELLE_LIGNE
.. cellule({ etiquette("DO", "DO :"), fond = FOND_ETIQUETTE, arrondi = ARRONDI_A_GAUCHE })
.. cellule({ texte_do(f.args), fond = FOND_VALEUR, arrondi = ARRONDI_A_DROITE })
.. cellules_capacite(f.args, 3)
if f.a_ruban2 then
texte = texte .. cellule_ruban(val(f.args, "ruban2"), val(f.args, "taille-ruban2"), 2)
end
return texte
end
--- Ligne 5 : la Ball et le nom (sous le sprite), l'ID, la capacité 4.
local function ligne_nom_et_id(f)
return NOUVELLE_LIGNE
.. cellule({ texte_nom(f.args), fond = FOND_VALEUR, css = "font-size:90%;" })
.. cellule({ etiquette("ID", "ID :"), fond = FOND_ETIQUETTE, arrondi = ARRONDI_A_GAUCHE })
.. cellule({ texte_id(f.args), fond = FOND_VALEUR, arrondi = ARRONDI_A_DROITE })
.. cellules_capacite(f.args, 4)
end
--- Lignes 6 et 7 : les étiquettes, puis les valeurs de l'objet tenu, du
-- talent et de la nature. Sans objet, le talent et la nature se partagent
-- toute la largeur.
local function lignes_objet_talent_nature(f)
local cases = f.a_objet and 2 or 3
local function entete(page, texte, colspan)
return cellule({ etiquette(page, texte), colspan = colspan,
fond = FOND_ETIQUETTE, arrondi = ARRONDI_EN_HAUT })
end
local function valeur(texte, colspan, css)
return cellule({ texte, colspan = colspan, fond = FOND_VALEUR,
arrondi = ARRONDI_EN_BAS, css = "font-size:85%;" .. (css or "") })
end
local etiquettes, valeurs = NOUVELLE_LIGNE, NOUVELLE_LIGNE
if f.a_objet then
etiquettes = etiquettes .. entete("Objet tenu", "Objet tenu :", 2)
valeurs = valeurs .. valeur(texte_objet(f.args), 2)
end
local cache = oui(f.args, "talentcaché")
local nature = val(f.args, "nature")
etiquettes = etiquettes .. entete("Talent", "Talent :", cases)
.. entete("Nature", "Nature :", cases)
valeurs = valeurs
.. valeur(gras(texte_talent(f.args), cache), cases, " " .. css_gras(cache))
.. valeur(sans_talent_ni_nature(f.args) and AVANT_LA_TROISIEME
or (nature ~= "" and nature or "Au hasard"), cases)
return etiquettes .. valeurs
end
-- LES LIGNES PLEINE LARGEUR, EN BAS DE LA FICHE
-- Chaque fonction `texte_*` rend le contenu de sa ligne, ou "" pour
-- l'omettre.
--- Le séparateur entre deux données d'une même ligne. L'espace insécable
-- colle le point à la donnée qui le précède : une ligne coupée ne
-- commence jamais par « · ».
local SEPARATEUR = " · "
local function texte_origine(args)
local origine = val(args, "origine")
if origine == "" then return "" end
-- Une valeur telle que HOME l'écrit : « Marque de Galar », « Pentagone bleu ».
return etiquette("Origine", "Origine :") .. " [[Fichier:Icône " .. origine
.. " HOME.png|25px]] " .. origine
end
--- Une date écrite avec la précision qu'on connaît : « 2004 »,
-- « juillet 2004 » ou « 17 juillet 2004 ». Rend aussi la précision.
-- ⚠️ `{{#time:}}` lit « 2004 » seul comme l'heure 20 h 04 : une année ne
-- lui est jamais passée. Une valeur d'une autre forme lui est passée
-- telle quelle, pour qu'il affiche son erreur en rouge : une date fausse
-- qui s'affiche quand même serait pire qu'une date absente.
-- `j xg Y` écrit le mois au génitif (« 17 juillet »), `F Y` au nominatif.
local function date_lisible(v)
if v:match("^%d%d%d%d$") then
return v, "annee"
elseif v:match("^%d%d%d%d%-%d%d$") then
return "{{#time:F Y|" .. v .. "-01}}", "mois"
end
return "{{#time:j xg Y|" .. v .. "}}", "jour"
end
--- La date affichée. Trois paramètres, trois rôles :
-- date-debut le premier jour : 2004, 2004-07 ou 2004-07-17
-- date-fin le dernier jour, sous les mêmes formes
-- date le texte libre, pour une période avec des coupures
-- (« du 30 août au 26 septembre 2002, puis du 1er au
-- 7 novembre ») ou une date floue (« Été 1996 »)
-- Le texte libre, quand il existe, gagne à l'affichage. Les deux bornes
-- restent alors des données, pour trier et chercher.
local function texte_de_date(args)
local libre = val(args, "date")
if libre ~= "" then return libre end
local debut, fin = val(args, "date-debut"), val(args, "date-fin")
if debut == "" then return "" end
local d, precision = date_lisible(debut)
if fin == "" then
if precision == "jour" then return "Le " .. d end
return "En " .. d
end
local f, precision_fin = date_lisible(fin)
if precision == "jour" and precision_fin == "jour" then
return "Du " .. d .. " au " .. f
end
return "De " .. d .. " à " .. f
end
--- La date et le canal : Wi-Fi, code de série…
local function texte_distribution(args)
local date = texte_de_date(args)
local canal = table.concat(valeurs_admises(args, "canal", CANAUX), ", ")
if date == "" and canal == "" then return "" end
return etiquette("Événement Nintendo", "Distribution :") .. " "
.. joindre_non_vides({ date, canal }, " — ")
end
--- Les jeux où la distribution était recevable. Pas de lien : la page
-- n'existe pas.
local function texte_versions(args)
local versions = val(args, "versions")
if versions == "" then return "" end
return "<small>'''Recevable dans :'''</small> " .. versions
end
--- La Carte Cadeau : la carte que le jeu affiche quand le joueur reçoit
-- le Pokémon. Son titre en français, et son numéro.
local function texte_carte_cadeau(args)
local titre, numero = val(args, "carte-titre"), val(args, "carte-numero")
if titre == "" and numero == "" then return "" end
-- Une Carte Miracle avec son message a sa propre boîte (boite_carte).
if val(args, "carte-message") ~= "" and numero == "" then return "" end
return etiquette("Cadeau Mystère", "Carte Cadeau :") .. " "
.. joindre_non_vides({
titre ~= "" and ("''" .. titre .. "''") or "",
numero ~= "" and ("<small>(n° " .. numero .. ")</small>") or "",
}, " ")
end
--- Les six IV, dans l'ordre du jeu : le paramètre, puis son libellé.
local SIX_IV = {
{ "iv-pv", "PV" }, { "iv-att", "Att." }, { "iv-def", "Déf." },
{ "iv-attspe", "Att. Spé." }, { "iv-defspe", "Déf. Spé." },
{ "iv-vit", "Vit." },
}
--- Les cinq IV des deux premières générations, de 0 à 15 : l'Attaque
-- spéciale et la Défense spéciale n'y font qu'une, le Spécial.
local CINQ_IV = {
{ "iv-pv", "PV" }, { "iv-att", "Att." }, { "iv-def", "Déf." },
{ "iv-vit", "Vit." }, { "iv-special", "Spécial" },
}
--- Les IV : un par statistique quand on les connaît, sinon la phrase libre.
-- ⚠️ « 3 IV à 31 » ne dit pas LESQUELS. Les deux formes coexistent donc.
-- `iv-special` rempli fait passer la fiche aux cinq IV des 1ʳᵉ et 2ᵉ générations.
local function texte_iv(args)
local morceaux, detaille = {}, false
for _, iv in ipairs(val(args, "iv-special") ~= "" and CINQ_IV or SIX_IV) do
local v = val(args, iv[1])
if v ~= "" then detaille = true end
table.insert(morceaux, iv[2] .. " " .. ou_tiret(v))
end
local texte = detaille and table.concat(morceaux, SEPARATEUR) or val(args, "iv")
if texte == "" then return "" end
return etiquette("IV", "IV garantis :") .. " " .. texte
end
local function texte_statistiques(args)
local ev = val(args, "ev")
return joindre_non_vides({
texte_iv(args),
ev ~= "" and (etiquette("EV", "EV :") .. " " .. ev) or "",
}, SEPARATEUR)
end
--- Le jeu d'où vient le Pokémon, quand il n'est pas reçu par une
-- distribution : Pokémon Ranger, Pokémon Battle Revolution…
local function texte_obtenu_depuis(args)
local jeux = val(args, "obtenu-depuis")
if jeux == "" then return "" end
return "<small>'''Obtenu depuis :'''</small> " .. jeux
end
--- Une date de rencontre ou d'Œuf, au jour près : « le 17 mars 2009 ».
local function le_jour(v)
if v == "" then return "" end
return "le " .. (date_lisible(v))
end
local function texte_rencontre(args)
local lieu = val(args, "lieu-rencontre")
local niveau = val(args, "niveau-rencontre")
if lieu ~= "" and niveau ~= "" then
lieu = lieu .. " (niveau " .. niveau .. ")"
end
local date = val(args, "date-rencontre")
if lieu ~= "" and date ~= "" then
lieu = lieu .. ", " .. le_jour(date)
elseif date ~= "" then
lieu = le_jour(date)
end
local oeuf = joindre_non_vides({ val(args, "lieu-oeuf"), le_jour(val(args, "date-oeuf")) }, ", ")
return joindre_non_vides({
lieu ~= "" and ("<small>'''Rencontre :'''</small> " .. lieu) or "",
oeuf ~= "" and ("<small>'''Œuf reçu :'''</small> " .. oeuf) or "",
oui(args, "fatidique") and ("{{Infobulle|rencontre fatidique|Ce Pokémon "
.. "porte le marqueur de rencontre fatidique, exigé par certains "
.. "événements en jeu.}}") or "",
}, SEPARATEUR)
end
--- Les bandeaux explicatifs du bas de la fiche, dans l'ordre.
local function bandeaux(args)
local out = {}
for i = 1, 4 do
if oui(args, "cap-even" .. i) then
table.insert(out, "Une capacité en '''gras''' est normalement "
.. "inaccessible à ce Pokémon, selon son espèce ou son niveau.")
break
end
end
if oui(args, "talentcaché") then
table.insert(out, "Un talent en '''gras''' est un talent caché, "
.. "uniquement accessible par certaines méthodes.")
end
-- Plusieurs DO suffisent à dire que le DO varie ; `dovarie` reste
-- accepté pour les fiches qui n'en écrivent qu'un.
local nom_varie = oui(args, "nomvarie")
local do_varie = oui(args, "dovarie") or #les_do(args) > 1
local sujet
if nom_varie and do_varie then
sujet = "Le nom et le [[Dresseur d'Origine|DO]] de ce Pokémon varient"
elseif nom_varie then
sujet = "Le nom de ce Pokémon varie"
elseif do_varie then
sujet = "Le [[Dresseur d'Origine|DO]] de ce Pokémon varie"
end
if sujet then
table.insert(out, sujet .. " en fonction de la langue du jeu "
.. "dans lequel il a été obtenu.")
end
-- Les notes que le jeu impose, reprises du modèle de Bulbapedia
-- (4ᵉ génération, 2026-09-26).
local selon = val(args, "date-selon")
if selon == "boutique" then
table.insert(out, "La date de rencontre est celle de la console le jour où le Pokémon "
.. "a été récupéré auprès du livreur de la [[Boutique Pokémon]].")
elseif selon == "eclosion" then
table.insert(out, "Ce Pokémon a été distribué dans un Œuf : sa date de rencontre est "
.. "celle de son éclosion, et diffère pour chaque Pokémon.")
end
if oui(args, "ball-varie") then
table.insert(out, "La Ball de ce Pokémon dépend de celle utilisée pour le capturer.")
end
local langue_origine = val(args, "langue-origine")
if langue_origine ~= "" then
-- « l'anglais », « le japonais » : l'article suit la première lettre.
local article = langue_origine:match("^[aeiouyéèêAEIOUY]") and "l'" or "le "
table.insert(out, "La langue d'origine de ce Pokémon est " .. article .. langue_origine
.. ", quelle que soit la langue du jeu qui l'a reçu.")
end
if oui(args, "coreen") then
table.insert(out, "Les caractères coréens n'apparaissent pas dans les jeux non coréens.")
end
if oui(args, "beaute-max") then
table.insert(out, "La Beauté et le Lustre de ce Pokémon sont au maximum.")
end
local oublier = val(args, "oublier")
if oublier ~= "" then
local depuis = val(args, "oublier-depuis")
-- 3ᵉ génération : le Parc des Amis ; 4ᵉ génération : le Poké Transfert.
local par = val(args, "oublier-par")
table.insert(out, "Ce Pokémon doit oublier la capacité [[" .. oublier .. "]] pour être "
.. "envoyé par le " .. (par ~= "" and par or "[[Poké Transfert]]")
.. (depuis ~= "" and (" depuis " .. depuis) or "") .. ".")
end
return out
end
-- LA CARTE MIRACLE ET LA LETTRE, À CÔTÉ DE LA FICHE
local STYLE_BOITE = "display:inline-block; vertical-align:top; margin:0 0 1em 2em; "
.. "max-width:300px; text-align:left; font-size:90%;"
--- Une boîte à la manière de Bulbapedia : un en-tête, puis des lignes.
local function boite(entete, lignes, fond_entete, fond)
-- ⚠️ `{|` et `|}` doivent commencer une ligne : collés au `<div>`, le
-- tableau s'affichait en texte brut (Raikou Crown, 2026-09-26).
local out = { '\n<div style="' .. STYLE_BOITE .. '">\n',
'{| style="width:300px; background:' .. fond .. '; border:2px solid #485060; border-radius:8px;"',
'\n! style="background:' .. fond_entete .. '; border-radius:6px;" | ' .. entete }
for _, ligne in ipairs(lignes) do
if ligne ~= "" then
table.insert(out, '\n|-\n| style="background:#f8f8ff; border-radius:6px;" | ' .. ligne)
end
end
table.insert(out, "\n|}\n</div>")
return table.concat(out)
end
--- Les icônes de la carte : `carte-icones = 0251, 0492` → deux miniatures.
local function icones(args)
local out = {}
for numero in val(args, "carte-icones"):gmatch("[^,%s]+") do
table.insert(out, "{{miniature|" .. numero .. "}}")
end
return table.concat(out, " ")
end
--- La Carte Miracle que le joueur reçoit avec le Pokémon : son titre et son
-- message dans la langue de la carte, puis leur traduction.
-- En 5ᵉ génération, la carte a aussi une description (`carte-description`),
-- le texte que le jeu montre en la lisant.
local function boite_carte(args)
local message, description = val(args, "carte-message"), val(args, "carte-description")
if message == "" and description == "" then return "" end
local langue = val(args, "carte-langue")
local entete = "[[Carte Miracle]]" .. (langue ~= "" and (" <small>(en " .. langue .. ")</small>") or "")
.. " " .. icones(args)
local titre_fr, message_fr = val(args, "carte-titre-fr"), val(args, "carte-message-fr")
local description_fr = val(args, "carte-description-fr")
local traduction = ""
if titre_fr ~= "" or message_fr ~= "" or description_fr ~= "" then
traduction = "<small>'''Traduction :''' "
.. joindre_non_vides({ titre_fr ~= "" and ("''" .. titre_fr .. "''") or "", message_fr,
description_fr }, "<br>")
.. "</small>"
end
local titre = val(args, "carte-titre")
return boite(entete, { titre ~= "" and ("'''" .. titre .. "'''") or "", message, description, traduction },
"#d0b0f8", "#c0e0f8")
end
--- La lettre que tient le Pokémon, et sa traduction.
local function boite_lettre(args)
local message = val(args, "lettre-message")
if message == "" then return "" end
local traduction = val(args, "lettre-traduction")
local objet = val(args, "objet")
return boite("Lettre" .. (objet ~= "" and (" <small>(" .. objet .. ")</small>") or ""),
{ message, traduction ~= "" and ("<small>'''Traduction :''' " .. traduction .. "</small>") or "" },
"#f8e0a0", "#fff8e0")
end
--- Les lignes pleine largeur, dans l'ordre. Une donnée absente n'a pas
-- de ligne.
-- ⚠️ La taille du texte a trois états : 85 % pour les données, 75 % pour
-- les bandeaux, la taille normale pour l'origine et la distribution.
local function lignes_du_bas(f)
local function pleine(contenu, fond, taille, rayon)
if contenu == "" then return "" end
return NOUVELLE_LIGNE .. cellule({ contenu, colspan = f.largeur, fond = fond,
arrondi = rayon, css = taille and ("font-size:" .. taille .. ";") })
end
local a = f.args
local out = {
pleine(texte_origine(a), FOND_ETIQUETTE, nil, ARRONDI_EN_HAUT),
pleine(texte_distribution(a), FOND_ETIQUETTE),
pleine(texte_versions(a), FOND_VALEUR, "85%"),
pleine(texte_obtenu_depuis(a), FOND_VALEUR, "85%"),
pleine(texte_carte_cadeau(a), FOND_VALEUR, "85%"),
pleine(texte_statistiques(a), FOND_VALEUR, "85%"),
pleine(texte_rencontre(a), FOND_VALEUR, "85%"),
}
for _, bandeau in ipairs(bandeaux(a)) do
table.insert(out, pleine(bandeau, FOND_ENTETE, "75%"))
end
return table.concat(out)
end
-- LES DONNÉES INTERROGEABLES, INVISIBLES POUR LE LECTEUR
--- Range les données de la fiche dans Semantic MediaWiki, installé sur
-- Poképédia. Une requête `{{#ask:}}`, sur une autre page, peut ensuite
-- les rassembler : par exemple, toutes les distributions de Mew.
-- Chaque propriété est déclarée par une page `Attribut:Distribution …`.
local function donnees_semantiques(f)
local args = f.args
local morceaux = {}
local function poser(nom, valeur)
-- ⚠️ Une valeur balisée n'est pas une donnée, et sa barre
-- verticale couperait la liste de paires.
if valeur ~= "" and not valeur:find("[{}|%[%]]") then
table.insert(morceaux, nom .. "=" .. valeur)
end
end
poser("Distribution espèce", val(args, "pokemon"))
poser("Distribution jeu", val(args, "jeu"))
for _, v in ipairs(les_do(args)) do
poser("Distribution DO", v)
end
-- ⚠️ Si le DO est le joueur, l'ID est le sien : l'exposer
-- donnerait une fausse clé de rapprochement.
poser("Distribution ID", (do_est_le_joueur(args) or est_inconnu(val(args, "id"))) and "" or val(args, "id"))
poser("Distribution niveau", val(args, "niveau"))
poser("Distribution début", val(args, "date-debut"))
poser("Distribution fin", val(args, "date-fin"))
poser("Distribution carte cadeau", val(args, "carte-numero"))
poser("Distribution événement", f.evenement)
-- La clé commune à toutes les copies d'une même distribution
-- (`Mew-Aura-2007`) : la page du Pokémon regroupe les copies par elle.
poser("Distribution clé", val(args, "distribution"))
-- Une propriété à plusieurs valeurs se répète : `Distribution pays=
-- Norvège|Distribution pays=Suède`. Une requête « Norvège » la trouve.
for _, champ in ipairs({ { "Distribution région", "region", REGIONS },
{ "Distribution pays", "pays", PAYS }, { "Distribution canal", "canal", CANAUX },
{ "Distribution langue", "langue", LANGUES } }) do
for _, v in ipairs((valeurs_admises(args, champ[2], champ[3]))) do
poser(champ[1], v)
end
end
if #morceaux == 0 then return "" end
-- Un ordre fixe, pour une sortie stable.
table.sort(morceaux)
-- ⚠️ `#subobject` et non `#set` : `#set` attache les valeurs à la PAGE,
-- et les 20 à 120 fiches d'une page de liste se mélangeraient. Le
-- premier paramètre vide donne à chaque fiche son propre paquet.
return "{{#subobject:|" .. table.concat(morceaux, "|") .. "}}"
end
-- LA GARDE DE MAINTENANCE
--- Un paramètre porte-t-il un nom que le modèle ne comprend pas ?
-- `nomvaire` (faute de frappe), `Ball` (majuscule), `img_ball` (ancien
-- paramètre), `1` (une barre verticale en trop dans la saisie)…
local function a_un_parametre_inconnu(args)
-- Scribunto permet `pairs()` sur ses arguments, mais pas `next()`.
for nom in pairs(args) do
if not PARAMETRES_CONNUS[nom] then return true end
end
return false
end
--- Une valeur de `region`, `pays`, `canal` ou `langue` est-elle hors de sa liste ?
local function a_une_valeur_hors_liste(args)
for champ, admises in pairs({ region = REGIONS, pays = PAYS, canal = CANAUX, langue = LANGUES }) do
if select(2, valeurs_admises(args, champ, admises)) then return true end
end
return false
end
--- Range la page parmi celles à réparer, quand la saisie se contredit,
-- contient un paramètre inconnu ou une valeur hors liste.
local function categorie_de_maintenance(args)
local fautes = {
a_une_valeur_hors_liste(args),
-- une fiche sans clé : ajoutée à la main, elle compterait comme une
-- distribution de plus sur la page du Pokémon
val(args, "distribution") == "",
-- une taille de ruban sans le ruban qu'elle habille
val(args, "taille-ruban") ~= "" and not porte_un_ruban(args, "ruban"),
val(args, "taille-ruban2") ~= "" and not porte_un_ruban(args, "ruban2"),
-- un second ruban sans le premier
porte_un_ruban(args, "ruban2") and not porte_un_ruban(args, "ruban"),
-- Téracristal et Gigamax ensemble : le module n'en rend qu'un
val(args, "type-téra") ~= "" and oui(args, "gigamax"),
-- un objet « au transfert » hors de la 1ʳᵉ génération, ou sans objet :
-- le mécanisme n'existe qu'entre la 1ʳᵉ et la 2ᵉ génération
objet_au_transfert(args) and (not est_de_la_premiere_generation(args) or val(args, "objet") == ""),
a_un_parametre_inconnu(args),
}
for _, faute in ipairs(fautes) do
if faute then return CATEGORIE_DE_MAINTENANCE end
end
return ""
end
-- L'ASSEMBLAGE
--- Les arguments de l'appel : ceux du modèle, ou ceux de `#invoke`.
-- Aucune page n'appelle `#invoke` directement : le second cas sert aux
-- essais.
local function arguments(frame)
-- ⚠️ Jamais de `next()` sur ces arguments : Scribunto les bâtit par
-- métatable, et `next()` y rend toujours nil.
-- ⚠️ `getParent()` rend nil sur un appel direct depuis une page.
local parent = frame:getParent()
if parent == nil then return frame.args end
if parent.args.pokemon == nil and frame.args.pokemon ~= nil then
return frame.args
end
return parent.args
end
--- Ce que la fiche contient, calculé une fois pour toutes les lignes.
local function decrire(args)
local a_ruban = porte_un_ruban(args, "ruban")
return {
args = args,
a_tera = val(args, "type-téra") ~= "",
a_gigamax = oui(args, "gigamax"),
a_objet = val(args, "objet") ~= "",
a_ruban = a_ruban,
-- ⚠️ Un second ruban n'existe que si le premier existe : sinon, une
-- cellule sans en-tête décale tout le tableau.
a_ruban2 = a_ruban and porte_un_ruban(args, "ruban2"),
largeur = a_ruban and 6 or 5,
-- Le nom de la distribution, sans ses liens : il sert d'ancre et
-- de donnée (« Mew Aura »).
evenement = sans_liens(val(args, "evenement")),
}
end
function p.fiche(frame)
local f = decrire(arguments(frame))
local out = {
ouverture(f),
ligne_entetes(f),
ligne_types(f),
ligne_tera_gigamax(f),
ligne_do(f),
ligne_nom_et_id(f),
lignes_objet_talent_nature(f),
lignes_du_bas(f),
"\n|}\n</div>",
boite_carte(f.args),
boite_lettre(f.args),
donnees_semantiques(f),
categorie_de_maintenance(f.args),
}
-- ⚠️ `preprocess` est indispensable : la sortie d'un module est
-- insérée APRÈS l'expansion, donc nos {{…}} resteraient en clair.
return frame:preprocess(table.concat(out))
end
return p