v2.6.1 : encyclopedie - plages potions/parchemins depuis la BDD, cache, fix pufPuff
- /api/reference/(gear|monsters|potions|scrolls) generalise ; treasures.js lit aussi les plages de puissance potions/parchemins (powerLabel garde les libelles speciaux -, X (fixe), set discret). - Cache-Control: no-cache sur html/js/css (revalidation systematique) pour que les MAJ et editions BDD soient prises sans vider le cache ; medias en cache 1j. - POTION_POWER_DEFAULTS.pufPuff 0-2 -> 1-3 (incoherent avec le rollPower solo corrige en 2.5.1) + migration db.init() des bases existantes (UPDATE cible). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -234,6 +234,17 @@ non affilié au jeu original de Mountyhall SARL.
|
|||||||
|
|
||||||
## Versions
|
## 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
|
- **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
|
é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`
|
valeurs figées du code : nouvel endpoint `GET /api/reference/gear`
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const POTION_POWER_DEFAULTS = {
|
|||||||
fertilite: [3, 7], feu: [3, 7], longueVue: [1, 8], kouleMann: [1, 5],
|
fertilite: [3, 7], feu: [3, 7], longueVue: [1, 8], kouleMann: [1, 5],
|
||||||
djhinTonik: [1, 5], glacier: [3, 7], calvok: [2, 6], rhume: [1, 2],
|
djhinTonik: [1, 5], glacier: [3, 7], calvok: [2, 6], rhume: [1, 2],
|
||||||
grippe: [3, 4], pneumonie: [5, 5], cervelle: [2, 6], chronometre: [1, 5],
|
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],
|
sangToh: [1, 5], sinneKhole: [11, 100], toxine: [1, 5], voiputrin: [1, 5],
|
||||||
zetCrak: [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 (?, ?, ?, ?, ?)`);
|
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);
|
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;
|
return DB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
"use strict";
|
"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. */
|
/* Alpha : maîtrise initiale haute pour les tests. Remettre 15 % / 15 % à la v1.0 officielle. */
|
||||||
const START_COMP_PCT = 90;
|
const START_COMP_PCT = 90;
|
||||||
|
|||||||
+43
-15
@@ -72,13 +72,13 @@ function treasuresRender() {
|
|||||||
|
|
||||||
html += `<h2 class="tz-section">🧪 Potions (${TREASURE_POTIONS.length})</h2><div class="tz-grid">`;
|
html += `<h2 class="tz-section">🧪 Potions (${TREASURE_POTIONS.length})</h2><div class="tz-grid">`;
|
||||||
for (const [id, x, duration, fx] of TREASURE_POTIONS) {
|
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 += "</div>";
|
html += "</div>";
|
||||||
|
|
||||||
html += `<h2 class="tz-section">📜 Parchemins standards (${TREASURE_SCROLLS.length})</h2><div class="tz-grid">`;
|
html += `<h2 class="tz-section">📜 Parchemins standards (${TREASURE_SCROLLS.length})</h2><div class="tz-grid">`;
|
||||||
for (const [id, x, duration, fx] of TREASURE_SCROLLS) {
|
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 += "</div>";
|
html += "</div>";
|
||||||
|
|
||||||
@@ -126,16 +126,32 @@ function treasuresRender() {
|
|||||||
|
|
||||||
let treasuresReturnTo = "create";
|
let treasuresReturnTo = "create";
|
||||||
|
|
||||||
// Équipements de la base de référence (BDD), rechargés à chaque ouverture pour
|
// Données de la base de référence (BDD), rechargées à chaque ouverture pour
|
||||||
// refléter les éditions ; null tant qu'on n'a pas (ou pas pu) charger → repli
|
// refléter les éditions (admin OU sqlite-web) ; null tant qu'on n'a pas (ou pas
|
||||||
// sur les valeurs statiques de gear.js.
|
// pu) charger → repli sur les valeurs statiques du code.
|
||||||
let GEAR_DB = null;
|
let GEAR_DB = null, POTIONS_DB = null, SCROLLS_DB = null;
|
||||||
async function loadGearReference() {
|
|
||||||
|
async function fetchRef(cat) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("api/reference/gear");
|
const res = await fetch("api/reference/" + cat);
|
||||||
if (!res.ok) throw new Error("indisponible");
|
if (!res.ok) return null;
|
||||||
const rows = await res.json();
|
const rows = await res.json();
|
||||||
if (!Array.isArray(rows) || !rows.length) throw new Error("vide");
|
return Array.isArray(rows) && rows.length ? rows : null;
|
||||||
|
} catch {
|
||||||
|
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 = {};
|
const bySlot = {};
|
||||||
for (const r of rows) {
|
for (const r of rows) {
|
||||||
const mods = {};
|
const mods = {};
|
||||||
@@ -146,10 +162,22 @@ async function loadGearReference() {
|
|||||||
note: staticDef && staticDef.note, mods,
|
note: staticDef && staticDef.note, mods,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
GEAR_DB = bySlot;
|
return bySlot;
|
||||||
} catch {
|
}
|
||||||
GEAR_DB = null; // base injoignable (ex. ouverture en file://) → statique
|
|
||||||
}
|
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) {
|
async function treasuresShow(from) {
|
||||||
@@ -157,7 +185,7 @@ async function treasuresShow(from) {
|
|||||||
document.getElementById("screen-" + from).classList.add("hidden");
|
document.getElementById("screen-" + from).classList.add("hidden");
|
||||||
document.getElementById("screen-treasures").classList.remove("hidden");
|
document.getElementById("screen-treasures").classList.remove("hidden");
|
||||||
document.getElementById("screen-treasures").scrollTop = 0;
|
document.getElementById("screen-treasures").scrollTop = 0;
|
||||||
await loadGearReference();
|
await loadReference();
|
||||||
treasuresRender();
|
treasuresRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -258,11 +258,18 @@ function handleMP(req, res, url) {
|
|||||||
function handleAPI(req, res, url) {
|
function handleAPI(req, res, url) {
|
||||||
if (url.pathname.startsWith("/api/mp/")) return handleMP(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 :
|
// Valeurs de référence lues EN DIRECT dans la BDD SQLite : l'encyclopédie
|
||||||
// l'encyclopédie « Trésors du Hall » reflète ainsi les éditions de la base
|
// « Trésors du Hall » reflète ainsi les éditions de la base, qu'elles viennent
|
||||||
// (DB Browser / sqlite-web) sans toucher au code.
|
// de la page admin ou d'un outil comme sqlite-web — sans toucher au code.
|
||||||
if (req.method === "GET" && url.pathname === "/api/reference/gear") {
|
const REFERENCE = {
|
||||||
return sendJSON(res, 200, db.gearAll());
|
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})$/);
|
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) => {
|
fs.readFile(file, (err, data) => {
|
||||||
if (err) { res.writeHead(404); return res.end("introuvable"); }
|
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);
|
res.end(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user