diff --git a/README.md b/README.md index 82c3df9..466e8a8 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,17 @@ non affilié au jeu original de Mountyhall SARL. ## Versions +- **2.6.1** (2026-06-13) — Suite du branchement de la base de référence sur + l'encyclopédie. Les **plages de puissance des potions et parchemins** sont + désormais lues dans la BDD (`/api/reference/potions`, `/api/reference/scrolls`, + `/api/reference/monsters` en plus de `gear`) — une plage simple « A à B » suit + la base, les libellés spéciaux (« — », « 5 (fixe) », « 1, 2, 3, 5 ou 8 ») sont + préservés. **Cache navigateur corrigé** : le HTML/JS/CSS est désormais envoyé + en `Cache-Control: no-cache` (toujours revalidé) pour qu'une mise à jour ou une + édition de la base soit prise immédiatement, sans vider le cache à la main + (les médias gardent un cache d'un jour). Enfin, **PufPuff** était encore en + « 0 à 2 » dans la base (le rollPower solo seul avait été corrigé en 2.5.1) : + défaut corrigé en « 1 à 3 » + migration automatique des bases déjà créées. - **2.6.0** (2026-06-13) — La page **« Trésors du Hall »** lit désormais les équipements **en direct dans la base de référence** (BDD SQLite) au lieu des valeurs figées du code : nouvel endpoint `GET /api/reference/gear` diff --git a/db.js b/db.js index e294ed0..4097fcb 100644 --- a/db.js +++ b/db.js @@ -31,7 +31,7 @@ const POTION_POWER_DEFAULTS = { fertilite: [3, 7], feu: [3, 7], longueVue: [1, 8], kouleMann: [1, 5], djhinTonik: [1, 5], glacier: [3, 7], calvok: [2, 6], rhume: [1, 2], grippe: [3, 4], pneumonie: [5, 5], cervelle: [2, 6], chronometre: [1, 5], - metomol: [1, 5], guerison: [1, 5], painture: [1, 5], pufPuff: [0, 2], + metomol: [1, 5], guerison: [1, 5], painture: [1, 5], pufPuff: [1, 3], sangToh: [1, 5], sinneKhole: [11, 100], toxine: [1, 5], voiputrin: [1, 5], zetCrak: [1, 5], }; @@ -110,6 +110,10 @@ function init(file = ":memory:") { const ins = DB.prepare(`INSERT OR IGNORE INTO ${cat} (id, name, emoji, powerMin, powerMax) VALUES (?, ?, ?, ?, ?)`); for (const t of vanillaTreasures(cat)) ins.run(t.id, t.name, t.emoji, t.powerMin, t.powerMax); } + // Correctif des bases déjà créées : PufPuff avait été seedé à tort en [0,2] + // (le niveau 0 d'une potion n'existe pas, minimum 1) → on le ramène à [1,3]. + // Le garde `powerMin = 0` évite d'écraser une plage éditée volontairement. + DB.prepare("UPDATE potions SET powerMin = 1, powerMax = 3 WHERE id = 'pufPuff' AND powerMin = 0").run(); return DB; } diff --git a/js/game.js b/js/game.js index 8b57e9a..b6f0c36 100644 --- a/js/game.js +++ b/js/game.js @@ -4,7 +4,7 @@ "use strict"; -const APP_VERSION = "2.6.0"; +const APP_VERSION = "2.6.1"; /* Alpha : maîtrise initiale haute pour les tests. Remettre 15 % / 15 % à la v1.0 officielle. */ const START_COMP_PCT = 90; diff --git a/js/treasures.js b/js/treasures.js index 282b0a7..afd96f5 100644 --- a/js/treasures.js +++ b/js/treasures.js @@ -72,13 +72,13 @@ function treasuresRender() { html += `

🧪 Potions (${TREASURE_POTIONS.length})

`; for (const [id, x, duration, fx] of TREASURE_POTIONS) { - html += treasureCard(POTION_DEFS[id], x, duration, fx); + html += treasureCard(POTION_DEFS[id], powerLabel(x, POTIONS_DB && POTIONS_DB[id]), duration, fx); } html += "
"; html += `

📜 Parchemins standards (${TREASURE_SCROLLS.length})

`; for (const [id, x, duration, fx] of TREASURE_SCROLLS) { - html += treasureCard(SCROLL_DEFS[id], x, duration, fx); + html += treasureCard(SCROLL_DEFS[id], powerLabel(x, SCROLLS_DB && SCROLLS_DB[id]), duration, fx); } html += "
"; @@ -126,38 +126,66 @@ function treasuresRender() { let treasuresReturnTo = "create"; -// Équipements de la base de référence (BDD), rechargés à chaque ouverture pour -// refléter les éditions ; null tant qu'on n'a pas (ou pas pu) charger → repli -// sur les valeurs statiques de gear.js. -let GEAR_DB = null; -async function loadGearReference() { +// Données de la base de référence (BDD), rechargées à chaque ouverture pour +// refléter les éditions (admin OU sqlite-web) ; null tant qu'on n'a pas (ou pas +// pu) charger → repli sur les valeurs statiques du code. +let GEAR_DB = null, POTIONS_DB = null, SCROLLS_DB = null; + +async function fetchRef(cat) { try { - const res = await fetch("api/reference/gear"); - if (!res.ok) throw new Error("indisponible"); + const res = await fetch("api/reference/" + cat); + if (!res.ok) return null; const rows = await res.json(); - if (!Array.isArray(rows) || !rows.length) throw new Error("vide"); - const bySlot = {}; - for (const r of rows) { - const mods = {}; - for (const [key] of GEAR_MOD_LABELS) mods[key] = r[key] || 0; - const staticDef = (GEAR[r.slot] || []).find(d => d.name === r.name); - (bySlot[r.slot] = bySlot[r.slot] || []).push({ - emoji: r.emoji, name: r.name, tier: r.tier, twoHanded: !!r.twoHanded, - note: staticDef && staticDef.note, mods, - }); - } - GEAR_DB = bySlot; + return Array.isArray(rows) && rows.length ? rows : null; } catch { - GEAR_DB = null; // base injoignable (ex. ouverture en file://) → statique + return null; // base injoignable (ex. ouverture en file://) } } +async function loadReference() { + const [gear, potions, scrolls] = await Promise.all([ + fetchRef("gear"), fetchRef("potions"), fetchRef("scrolls"), + ]); + GEAR_DB = gear ? gearBySlotFromRows(gear) : null; + POTIONS_DB = potions ? indexPowerById(potions) : null; + SCROLLS_DB = scrolls ? indexPowerById(scrolls) : null; +} + +function gearBySlotFromRows(rows) { + const bySlot = {}; + for (const r of rows) { + const mods = {}; + for (const [key] of GEAR_MOD_LABELS) mods[key] = r[key] || 0; + const staticDef = (GEAR[r.slot] || []).find(d => d.name === r.name); + (bySlot[r.slot] = bySlot[r.slot] || []).push({ + emoji: r.emoji, name: r.name, tier: r.tier, twoHanded: !!r.twoHanded, + note: staticDef && staticDef.note, mods, + }); + } + return bySlot; +} + +function indexPowerById(rows) { + const map = {}; + for (const r of rows) map[r.id] = { min: r.powerMin, max: r.powerMax }; + return map; +} + +// Libellé « Niveau X » : on remplace par la plage BDD (min/max) UNIQUEMENT pour +// les plages numériques simples « A à B » ; les libellés spéciaux (« — », +// « 5 (fixe) », « 1, 2, 3, 5 ou 8 »…) sont conservés tels quels car la base ne +// stocke qu'un min/max qui ne saurait les représenter. +function powerLabel(staticLabel, range) { + if (!range || !/^\d+ à \d+$/.test(staticLabel)) return staticLabel; + return range.min === range.max ? `${range.min} (fixe)` : `${range.min} à ${range.max}`; +} + async function treasuresShow(from) { treasuresReturnTo = from; document.getElementById("screen-" + from).classList.add("hidden"); document.getElementById("screen-treasures").classList.remove("hidden"); document.getElementById("screen-treasures").scrollTop = 0; - await loadGearReference(); + await loadReference(); treasuresRender(); } diff --git a/server.js b/server.js index 6a64da4..aa574c3 100644 --- a/server.js +++ b/server.js @@ -258,11 +258,18 @@ function handleMP(req, res, url) { function handleAPI(req, res, url) { if (url.pathname.startsWith("/api/mp/")) return handleMP(req, res, url); - // Valeurs de référence des équipements lues EN DIRECT dans la BDD SQLite : - // l'encyclopédie « Trésors du Hall » reflète ainsi les éditions de la base - // (DB Browser / sqlite-web) sans toucher au code. - if (req.method === "GET" && url.pathname === "/api/reference/gear") { - return sendJSON(res, 200, db.gearAll()); + // Valeurs de référence lues EN DIRECT dans la BDD SQLite : l'encyclopédie + // « Trésors du Hall » reflète ainsi les éditions de la base, qu'elles viennent + // de la page admin ou d'un outil comme sqlite-web — sans toucher au code. + const REFERENCE = { + gear: () => db.gearAll(), + monsters: () => db.monsters(), + potions: () => db.treasuresAll("potions"), + scrolls: () => db.treasuresAll("scrolls"), + }; + const refMatch = url.pathname.match(/^\/api\/reference\/(gear|monsters|potions|scrolls)$/); + if (req.method === "GET" && refMatch) { + return sendJSON(res, 200, REFERENCE[refMatch[1]]()); } const idMatch = url.pathname.match(/^\/api\/levels\/([a-f0-9]{12})$/); @@ -333,7 +340,17 @@ function handleStatic(req, res, url) { } fs.readFile(file, (err, data) => { if (err) { res.writeHead(404); return res.end("introuvable"); } - res.writeHead(200, { "Content-Type": MIME[path.extname(file)] || "application/octet-stream" }); + const ext = path.extname(file); + // Code et pages (.html/.js/.css) : toujours revalider pour qu'une mise à + // jour (ou une édition de la base reflétée par le code) soit prise tout de + // suite, sans cache navigateur périmé. Médias : cache court. + const cache = [".html", ".js", ".css"].includes(ext) + ? "no-cache" + : "public, max-age=86400"; + res.writeHead(200, { + "Content-Type": MIME[ext] || "application/octet-stream", + "Cache-Control": cache, + }); res.end(data); }); }