v2.10.0 : bestiaire (etape 1, donnees) - table BDD + 150 monstres + table d'ages

Bestiaire fourni (945 entrees monstre x age) parse et replie selon le
modele demande par Perco : 1 ligne par monstre (js/bestiary.js, 150
monstres, stats de base = age le plus jeune en min/max + famille +
capacites + drapeaux vol/distance/magie/voit-cache + vitesse + nb attaques
+ blason) + table d'ages (8 multiplicateurs derives des donnees). db.js :
tables bestiary + monster_ages, seed, bestiaryAll/monsterAges,
setBestiary/setMonsterAge, resetCategory. server.js : /api/reference/
bestiary + /ages. Non destructif (le jeu tourne encore sur l'ancien
systeme). Etapes suivantes : encyclopedie, editeur admin, integration spawn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
percoandClaude Opus 4.8 committed 2026-06-15 16:12:56 +02:00
1 parent 28863303c6
commit 836837fa55
5 files changed
+70 -6

No files matched your search

+11 -1
View File
@@ -234,7 +234,17 @@ non affilié au jeu original de Mountyhall SARL.
## Versions ## Versions
- **2.9.1** (2026-06-15) — Le **solo reçoit tout ce que le monde partagé a eu** - **2.10.0** (2026-06-15) — **Nouveau bestiaire** (étape 1 : les données). Le
bestiaire complet fourni (945 entrées monstre × âge) est replié, comme demandé,
en **1 ligne par monstre** (150 monstres, stats de base = âge le plus jeune, en
plages min/max) + une **table d'âges** (8 multiplicateurs appliqués à la
génération/croissance). Tout est dans la base de référence (`js/bestiary.js` →
tables `bestiary` et `monster_ages`), exposé sur `/api/reference/bestiary` et
`/api/reference/ages`, et éditable comme le reste (sqlite-web). Chaque monstre
porte sa famille, ses plages de stats, ses capacités (listées, effets à venir),
ses drapeaux (vol, attaque à distance/magique, voit le caché), sa vitesse, son
nombre d'attaques et son blason. *Prochaines étapes : encyclopédie, éditeur
admin, puis bascule des apparitions du jeu sur ce bestiaire.*
(empilement des cases, rendu par coins avec le coin **lieu** ↘ qui affiche (empilement des cases, rendu par coins avec le coin **lieu** ↘ qui affiche
enfin les portes 🚪 / la sortie ▼, cases ×2, attaque limitée à sa propre case, enfin les portes 🚪 / la sortie ▼, cases ×2, attaque limitée à sa propre case,
drop empilable). Le **menu déroulant de cible s'affiche toujours**, même quand drop empilable). Le **menu déroulant de cible s'affiche toujours**, même quand
+46 -3
View File
@@ -19,6 +19,15 @@ const g = require("./js/game.js");
const p = require("./js/potions.js"); const p = require("./js/potions.js");
const sc = require("./js/scrolls.js"); const sc = require("./js/scrolls.js");
const gearLib = require("./js/gear.js"); const gearLib = require("./js/gear.js");
const bestiaryLib = require("./js/bestiary.js");
// Bestiaire complet (demande Perco) : 1 ligne par monstre (stats de base = âge
// le plus jeune, en plages min/max) + une table d'âges (multiplicateurs).
const BESTIARY_STATS = ["level", "pv", "att", "esq", "deg", "reg", "armPhys", "armMag", "vue", "mm", "rm"];
const BESTIARY_RANGE_KEYS = BESTIARY_STATS.flatMap(k => [k + "Min", k + "Max"]);
const BESTIARY_INT_KEYS = [...BESTIARY_RANGE_KEYS, "minAge", "maxAge", "fly", "ranged", "magic", "seesHidden", "nbAtt"];
const BESTIARY_TXT_KEYS = ["family", "speed", "capacities", "blason"];
const BESTIARY_KEYS = [...BESTIARY_INT_KEYS, ...BESTIARY_TXT_KEYS]; // hors `name` (clé)
const MONSTER_KEYS = ["level", "att", "attMag", "esq", "deg", "degMag", "pv", "armor", "armorMag", "vue"]; const MONSTER_KEYS = ["level", "att", "attMag", "esq", "deg", "degMag", "pv", "armor", "armorMag", "vue"];
const GEAR_KEYS = ["att", "attMag", "esq", "deg", "degMag", "reg", "arm", "armMag", "vue", "pv", "rmPct", "mmPct"]; const GEAR_KEYS = ["att", "attMag", "esq", "deg", "degMag", "reg", "arm", "armMag", "vue", "pv", "rmPct", "mmPct"];
@@ -129,6 +138,14 @@ function init(file = ":memory:") {
CREATE TABLE IF NOT EXISTS templates ( CREATE TABLE IF NOT EXISTS templates (
id INTEGER PRIMARY KEY, name TEXT, ${TEMPLATE_KEYS.map(k => `${k} INTEGER`).join(", ")} id INTEGER PRIMARY KEY, name TEXT, ${TEMPLATE_KEYS.map(k => `${k} INTEGER`).join(", ")}
); );
CREATE TABLE IF NOT EXISTS bestiary (
name TEXT PRIMARY KEY,
${BESTIARY_INT_KEYS.map(k => `${k} INTEGER`).join(", ")},
${BESTIARY_TXT_KEYS.map(k => `${k} TEXT`).join(", ")}
);
CREATE TABLE IF NOT EXISTS monster_ages (
age INTEGER PRIMARY KEY, mult REAL
);
`); `);
const insM = DB.prepare(`INSERT OR IGNORE INTO monsters const insM = DB.prepare(`INSERT OR IGNORE INTO monsters
(name, emoji, boss, isStatic, ${MONSTER_KEYS.join(", ")}) (name, emoji, boss, isStatic, ${MONSTER_KEYS.join(", ")})
@@ -149,6 +166,11 @@ function init(file = ":memory:") {
const insT = DB.prepare(`INSERT OR IGNORE INTO templates (id, name, ${TEMPLATE_KEYS.join(", ")}) const insT = DB.prepare(`INSERT OR IGNORE INTO templates (id, name, ${TEMPLATE_KEYS.join(", ")})
VALUES (?, ?, ${TEMPLATE_KEYS.map(() => "?").join(", ")})`); VALUES (?, ?, ${TEMPLATE_KEYS.map(() => "?").join(", ")})`);
for (const t of vanillaTemplates()) insT.run(t.id, t.name, ...TEMPLATE_KEYS.map(k => t[k])); for (const t of vanillaTemplates()) insT.run(t.id, t.name, ...TEMPLATE_KEYS.map(k => t[k]));
const insB = DB.prepare(`INSERT OR IGNORE INTO bestiary (name, ${BESTIARY_KEYS.join(", ")})
VALUES (?, ${BESTIARY_KEYS.map(() => "?").join(", ")})`);
for (const b of bestiaryLib.BESTIARY) insB.run(b.name, ...BESTIARY_KEYS.map(k => b[k]));
const insA = DB.prepare("INSERT OR IGNORE INTO monster_ages (age, mult) VALUES (?, ?)");
bestiaryLib.AGE_MULT.forEach((mult, age) => insA.run(age, mult));
// Correctif des bases déjà créées : PufPuff avait été seedé à tort en [0,2] // 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 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. // Le garde `powerMin = 0` évite d'écraser une plage éditée volontairement.
@@ -191,6 +213,14 @@ function templatesAll() {
return db().prepare("SELECT * FROM templates ORDER BY id").all(); return db().prepare("SELECT * FROM templates ORDER BY id").all();
} }
function bestiaryAll() {
return db().prepare("SELECT * FROM bestiary ORDER BY family, name").all();
}
function monsterAges() {
return db().prepare("SELECT * FROM monster_ages ORDER BY age").all();
}
/* ---------- Écriture (page admin) ---------- */ /* ---------- Écriture (page admin) ---------- */
function setMonster(name, vals) { function setMonster(name, vals) {
@@ -219,11 +249,24 @@ function setTemplate(id, vals) {
.run(...keys.map(k => vals[k]), id); .run(...keys.map(k => vals[k]), id);
} }
function setBestiary(name, vals) {
const keys = BESTIARY_KEYS.filter(k => vals[k] != null);
if (!keys.length) return;
db().prepare(`UPDATE bestiary SET ${keys.map(k => `${k} = ?`).join(", ")} WHERE name = ?`)
.run(...keys.map(k => vals[k]), name);
}
function setMonsterAge(age, mult) {
db().prepare("UPDATE monster_ages SET mult = ? WHERE age = ?").run(mult, age);
}
/* Remet une catégorie entière aux valeurs vanilla. */ /* Remet une catégorie entière aux valeurs vanilla. */
function resetCategory(cat) { function resetCategory(cat) {
if (cat === "monsters") for (const m of vanillaMonsters()) setMonster(m.name, m); if (cat === "monsters") for (const m of vanillaMonsters()) setMonster(m.name, m);
if (cat === "gear") for (const it of vanillaGear()) setGear(it.slot, it.name, it); if (cat === "gear") for (const it of vanillaGear()) setGear(it.slot, it.name, it);
if (cat === "templates") for (const t of vanillaTemplates()) setTemplate(t.id, t); if (cat === "templates") for (const t of vanillaTemplates()) setTemplate(t.id, t);
if (cat === "bestiary") for (const b of bestiaryLib.BESTIARY) setBestiary(b.name, b);
if (cat === "ages") bestiaryLib.AGE_MULT.forEach((mult, age) => setMonsterAge(age, mult));
if (cat === "potions" || cat === "scrolls") { if (cat === "potions" || cat === "scrolls") {
for (const t of vanillaTreasures(cat)) setTreasureRange(cat, t.id, [t.powerMin, t.powerMax]); for (const t of vanillaTreasures(cat)) setTreasureRange(cat, t.id, [t.powerMin, t.powerMax]);
} }
@@ -231,8 +274,8 @@ function resetCategory(cat) {
module.exports = { module.exports = {
init, db, init, db,
MONSTER_KEYS, GEAR_KEYS, TEMPLATE_KEYS, POTION_POWER_DEFAULTS, SCROLL_POWER_DEFAULTS, MONSTER_KEYS, GEAR_KEYS, TEMPLATE_KEYS, BESTIARY_KEYS, POTION_POWER_DEFAULTS, SCROLL_POWER_DEFAULTS,
vanillaMonsters, vanillaGear, vanillaTreasures, vanillaTemplates, vanillaMonsters, vanillaGear, vanillaTreasures, vanillaTemplates,
monsters, gearAll, gearRow, treasureRange, treasuresAll, templatesAll, monsters, gearAll, gearRow, treasureRange, treasuresAll, templatesAll, bestiaryAll, monsterAges,
setMonster, setGear, setTreasureRange, setTemplate, resetCategory, setMonster, setGear, setTreasureRange, setTemplate, setBestiary, setMonsterAge, resetCategory,
}; };
+9
View File
@@ -0,0 +1,9 @@
/* Bestiaire MountyCrawl — généré depuis le bestiaire fourni par Perco.
* 1 ligne par monstre (stats de base = âge le plus jeune), + une table d'âges
* (multiplicateurs appliqués à la génération/croissance). Plages min/max tirées
* au hasard à chaque apparition. 150 monstres, 6 familles. */
"use strict";
const BESTIARY = [{"name":"Ankheg","family":"Insecte","minAge":0,"maxAge":5,"levelMin":10,"levelMax":12,"pvMin":100,"pvMax":120,"attMin":9,"attMax":11,"esqMin":5,"esqMax":7,"degMin":4,"degMax":6,"regMin":1,"regMax":2,"armPhysMin":18,"armPhysMax":20,"armMagMin":0,"armMagMax":2,"vueMin":2,"vueMax":4,"mmMin":300,"mmMax":600,"rmMin":300,"rmMax":500,"fly":0,"ranged":0,"magic":0,"seesHidden":0,"speed":"Normale","nbAtt":1,"capacities":"Acide","blason":"https://www.mountyhall.com/images/Monstres/VG_Ankheg.jpg"},{"name":"Anoploure Purpurin","family":"Insecte","minAge":0,"maxAge":7,"levelMin":34,"levelMax":36,"pvMin":170,"pvMax":190,"attMin":19,"attMax":21,"esqMin":18,"esqMax":20,"degMin":18,"degMax":20,"regMin":12,"regMax":13,"armPhysMin":11,"armPhysMax":13,"armMagMin":0,"armMagMax":2,"vueMin":3,"vueMax":5,"mmMin":2300,"mmMax":2600,"rmMin":1800,"rmMax":2100,"fly":0,"ranged":0,"magic":0,"seesHidden":0,"speed":"Normale","nbAtt":1,"capacities":"Maladie","blason":"https://www.mountyhall.com/images/Monstres/BO_AnolporPurpurin.jpg"},{"name":"Araignée Géante","family":"Insecte","minAge":0,"maxAge":3,"levelMin":2,"levelMax":4,"pvMin":20,"pvMax":40,"attMin":2,"attMax":4,"esqMin":2,"esqMax":4,"degMin":2,"degMax":3,"regMin":1,"regMax":2,"armPhysMin":0,"armPhysMax":2,"armMagMin":0,"armMagMax":2,"vueMin":2,"vueMax":4,"mmMin":0,"mmMax":300,"rmMin":0,"rmMax":300,"fly":0,"ranged":0,"magic":0,"seesHidden":0,"speed":"Normale","nbAtt":1,"capacities":"Venin","blason":"https://www.mountyhall.com/images/Monstres/AraigneeGeante.jpg"},{"name":"Aragnarok du Chaos","family":"Insecte","minAge":0,"maxAge":3,"levelMin":14,"levelMax":16,"pvMin":50,"pvMax":70,"attMin":9,"attMax":11,"esqMin":9,"esqMax":11,"degMin":12,"degMax":14,"regMin":1,"regMax":2,"armPhysMin":2,"armPhysMax":4,"armMagMin":0,"armMagMax":2,"vueMin":3,"vueMax":5,"mmMin":600,"mmMax":900,"rmMin":800,"rmMax":1100,"fly":0,"ranged":0,"magic":0,"seesHidden":1,"speed":"Normale","nbAtt":1,"capacities":"Attaque Paralysante","blason":""},{"name":"Coccicruelle","family":"Insecte","minAge":0,"maxAge":3,"levelMin":20,"levelMax":22,"pvMin":90,"pvMax":110,"attMin":16,"attMax":18,"esqMin":12,"esqMax":14,"degMin":12,"degMax":14,"regMin":2,"regMax":3,"armPhysMin":6,"armPhysMax":8,"armMagMin":0,"armMagMax":2,"vueMin":4,"vueMax":6,"mmMin":800,"mmMax":1100,"rmMin":300,"rmMax":600,"fly":1,"ranged":0,"magic":0,"seesHidden":0,"speed":"Normale","nbAtt":1,"capacities":"","blason":"https://www.mountyhall.com/images/Monstres/FF_coccicruelle.png"},{"name":"Essaim Cratérien","family":"Insecte","minAge":0,"maxAge":7,"levelMin":28,"levelMax":30,"pvMin":250,"pvMax":270,"attMin":23,"attMax":25,"esqMin":11,"esqMax":13,"degMin":26,"degMax":28,"regMin":7,"regMax":8,"armPhysMin":37,"armPhysMax":39,"armMagMin":0,"armMagMax":2,"vueMin":7,"vueMax":9,"mmMin":3300,"mmMax":3600,"rmMin":3100,"rmMax":3400,"fly":1,"ranged":1,"magic":0,"seesHidden":1,"speed":"Lente","nbAtt":1,"capacities":"Attaque Paralysante","blason":"-"},{"name":"Essaim Sanguinaire","family":"Insecte","minAge":0,"maxAge":7,"levelMin":23,"levelMax":25,"pvMin":110,"pvMax":130,"attMin":13,"attMax":15,"esqMin":8,"esqMax":10,"degMin":15,"degMax":17,"regMin":3,"regMax":4,"armPhysMin":4,"armPhysMax":6,"armMagMin":0,"armMagMax":2,"vueMin":6,"vueMax":8,"mmMin":2300,"mmMax":2600,"rmMin":1900,"rmMax":2200,"fly":1,"ranged":1,"magic":0,"seesHidden":1,"speed":"Lente","nbAtt":1,"capacities":"Attaque Paralysante","blason":"https://www.mountyhall.com/images/Monstres/FF_essaim-sanguinaire.png"},{"name":"Foudroyeur","family":"Insecte","minAge":0,"maxAge":6,"levelMin":36,"levelMax":38,"pvMin":80,"pvMax":100,"attMin":22,"attMax":24,"esqMin":15,"esqMax":17,"degMin":21,"degMax":23,"regMin":5,"regMax":6,"armPhysMin":19,"armPhysMax":21,"armMagMin":0,"armMagMax":2,"vueMin":3,"vueMax":5,"mmMin":2800,"mmMax":3100,"rmMin":1800,"rmMax":2100,"fly":0,"ranged":0,"magic":0,"seesHidden":0,"speed":"Rapide","nbAtt":1,"capacities":"Attaque Electrique","blason":"https://www.mountyhall.com/images/Monstres/TO_Foudroyeur.jpg"},{"name":"Labeilleux","family":"Insecte","minAge":0,"maxAge":7,"levelMin":24,"levelMax":26,"pvMin":150,"pvMax":170,"attMin":18,"attMax":20,"esqMin":16,"esqMax":18,"degMin":12,"degMax":14,"regMin":5,"regMax":6,"armPhysMin":10,"armPhysMax":12,"armMagMin":0,"armMagMax":2,"vueMin":4,"vueMax":6,"mmMin":1300,"mmMax":1600,"rmMin":1800,"rmMax":2100,"fly":1,"ranged":0,"magic":0,"seesHidden":0,"speed":"Normale","nbAtt":1,"capacities":"Venin","blason":"https://www.mountyhall.com/images/Monstres/OF_Labeilleux.jpg"},{"name":"Limace Géante","family":"Insecte","minAge":0,"maxAge":3,"levelMin":10,"levelMax":12,"pvMin":70,"pvMax":90,"attMin":12,"attMax":14,"esqMin":2,"esqMax":2,"degMin":2,"degMax":4,"regMin":1,"regMax":2,"armPhysMin":1,"armPhysMax":2,"armMagMin":0,"armMagMax":2,"vueMin":2,"vueMax":4,"mmMin":200,"mmMax":300,"rmMin":100,"rmMax":300,"fly":0,"ranged":1,"magic":0,"seesHidden":0,"speed":"Normale","nbAtt":1,"capacities":"Attaque Gluante","blason":"https://www.mountyhall.com/image Line truncated
const AGE_NAMES = {"Insecte":["Larve","Immature","Juvénile","Imago","Développé","Mûr","Accompli","Achevé"],"Animal":["Bébé","Enfançon","Jeune","Adulte","Mature","Chef de harde","Ancien","Ancêtre"],"Démon":["Initial","Novice","Mineur","Favori","Majeur","Supérieur","Suprême","Ultime"],"Humanoide":["Nouveau","Jeune","Adulte","Vétéran","Briscard","Doyen","Légendaire","Mythique"],"Monstre":["Nouveau","Jeune","Adulte","Vétéran","Briscard","Doyen","Légendaire","Mythique"],"Mort-Vivant":["Naissant","Récent","Ancien","Vénérable","Séculaire","Antique","Ancestral","Antédiluvien"]};
const AGE_MULT = [1,1,1.13,1.15,1.19,1.33,1.35,1.35];
if (typeof module !== "undefined" && module.exports) module.exports = { BESTIARY, AGE_NAMES, AGE_MULT };
+1 -1
View File
@@ -4,7 +4,7 @@
"use strict"; "use strict";
const APP_VERSION = "2.9.1"; const APP_VERSION = "2.10.0";
/* 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;
+3 -1
View File
@@ -276,8 +276,10 @@ function handleAPI(req, res, url) {
potions: () => db.treasuresAll("potions"), potions: () => db.treasuresAll("potions"),
scrolls: () => db.treasuresAll("scrolls"), scrolls: () => db.treasuresAll("scrolls"),
templates: () => db.templatesAll(), templates: () => db.templatesAll(),
bestiary: () => db.bestiaryAll(),
ages: () => db.monsterAges(),
}; };
const refMatch = url.pathname.match(/^\/api\/reference\/(gear|monsters|potions|scrolls|templates)$/); const refMatch = url.pathname.match(/^\/api\/reference\/(gear|monsters|potions|scrolls|templates|bestiary|ages)$/);
if (req.method === "GET" && refMatch) { if (req.method === "GET" && refMatch) {
return sendJSON(res, 200, REFERENCE[refMatch[1]]()); return sendJSON(res, 200, REFERENCE[refMatch[1]]());
} }