v2.12.0 : editeur pose les monstres du bestiaire (mob+age) + retrait du Behemoth

game.js : rollBestiaryMonster(b, age, ..) extrait (build d'un monstre
precis) ; monsterFromSpec gere {mob:nom, age} (bestiaire) + retrocompat
{type,tpl} et boss:true ; bestiaryBoss() = le plus fort du bestiaire ;
buildLevel MAX_DEPTH -> bestiaryBoss (plus de Behemoth statique) ; messages
'gardien du Hall' ; applyLevel filtre les specs nulles. index.html : selects
famille/monstre/age dans l'editeur. editor.js : edSetupMobPickers/edFillMobs/
edFillAges, pinceau 'Poser le monstre choisi', placement {mob,age}, rendu
emoji de famille (retrocompat type/boss). server.js validateLevel accepte
{mob,age}. Behemoth (BOSS) garde comme repli si bestiaire absent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
perco
2026-06-17 11:21:45 +02:00
co-authored by Claude Opus 4.8
parent e2e749fa41
commit dc3685de11
5 changed files with 103 additions and 41 deletions
+40 -17
View File
@@ -102,22 +102,12 @@ function edBuildPalette() {
tilesDiv.innerHTML = "";
for (const t of tiles) edAddBrushBtn(tilesDiv, t.label, t);
const tplSel = document.getElementById("ed-tpl");
tplSel.innerHTML = "";
TEMPLATES.forEach((tpl, i) => {
const opt = document.createElement("option");
opt.value = i;
opt.textContent = (tpl.prefix.trim() || "Normal") + ` (×${tpl.mult})`;
if (i === 1) opt.selected = true;
tplSel.appendChild(opt);
});
// Monstres : on choisit dans le bestiaire (famille → monstre → âge) puis on
// pose la créature choisie. Un « Xorn [Adulte] », un « Nécrochore [Naissant] »…
edSetupMobPickers();
const monstersDiv = document.getElementById("ed-monsters");
monstersDiv.innerHTML = "";
MONSTER_TYPES.forEach((m, i) => {
edAddBrushBtn(monstersDiv, `${m.emoji} ${m.name} (niv. ${m.level})`, { mode: "monster", type: i });
});
edAddBrushBtn(monstersDiv, `${BOSS.emoji} ${BOSS.name} (boss)`, { mode: "monster", boss: true });
edAddBrushBtn(monstersDiv, "🐲 Poser le monstre choisi", { mode: "monster" });
const itemsDiv = document.getElementById("ed-items");
itemsDiv.innerHTML = "";
@@ -164,6 +154,36 @@ function edBuildPalette() {
toolsDiv.appendChild(caves);
}
/* Sélecteurs bestiaire de l'éditeur : famille → monstre → âge. */
function edBestiary() { return typeof BESTIARY !== "undefined" ? BESTIARY : []; }
function edSetupMobPickers() {
const famSel = document.getElementById("ed-mob-family");
const mobSel = document.getElementById("ed-mob");
if (!famSel.dataset.init) {
famSel.dataset.init = "1";
famSel.onchange = edFillMobs;
mobSel.onchange = edFillAges;
}
const fams = [...new Set(edBestiary().map(m => m.family))].sort();
famSel.innerHTML = '<option value="">Toutes</option>' + fams.map(f => `<option>${f}</option>`).join("");
edFillMobs();
}
function edFillMobs() {
const fam = document.getElementById("ed-mob-family").value;
const list = edBestiary().filter(m => !fam || m.family === fam).sort((a, b) => a.name.localeCompare(b.name));
document.getElementById("ed-mob").innerHTML = list.map(m => `<option>${m.name}</option>`).join("");
edFillAges();
}
function edFillAges() {
const b = edBestiary().find(m => m.name === document.getElementById("ed-mob").value);
const ageSel = document.getElementById("ed-mob-age");
if (!b) { ageSel.innerHTML = ""; return; }
const names = ((b.gender === "f" ? AGE_NAMES_F : AGE_NAMES) || {})[b.family] || [];
let html = "";
for (let a = b.minAge; a <= b.maxAge; a++) html += `<option value="${a}">${names[a] || "âge " + a}</option>`;
ageSel.innerHTML = html;
}
function edAddBrushBtn(parent, label, brush) {
const b = document.createElement("button");
b.textContent = label;
@@ -233,9 +253,10 @@ function edApply(x, y) {
ED.doors.push({ x, y, target });
} else if (b.mode === "monster") {
if (ED.grid[y][x] === "#") return;
const mob = document.getElementById("ed-mob").value;
if (!mob) return;
ED.monsters = ED.monsters.filter(m => m.x !== x || m.y !== y);
if (b.boss) ED.monsters.push({ x, y, boss: true });
else ED.monsters.push({ x, y, type: b.type, tpl: Number(document.getElementById("ed-tpl").value) });
ED.monsters.push({ x, y, mob, age: Number(document.getElementById("ed-mob-age").value) || 0 });
} else if (b.mode === "item") {
if (ED.grid[y][x] === "#") return;
ED.items = ED.items.filter(i => i.x !== x || i.y !== y);
@@ -291,9 +312,11 @@ function edRender() {
ctx.fillText("🚪", d.x * TILE + TILE / 2, d.y * TILE + TILE / 2 + 1);
}
for (const m of ED.monsters) {
const b = m.mob ? edBestiary().find(x => x.name === m.mob) : null;
disc(m.x, m.y, m.boss ? "#7a2070" : "#8a3030");
ctx.fillStyle = "#1a140e";
const emoji = m.boss ? BOSS.emoji : MONSTER_TYPES[m.type].emoji;
const emoji = m.mob ? (b ? (FAMILY_EMOJI_G[b.family] || "👹") : "👹")
: m.boss ? "👹" : (MONSTER_TYPES[m.type] ? MONSTER_TYPES[m.type].emoji : "👺");
ctx.fillText(emoji, m.x * TILE + TILE / 2, m.y * TILE + TILE / 2 + 1);
}
if (ED.start) {