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>
511 lines
19 KiB
JavaScript
511 lines
19 KiB
JavaScript
/* Éditeur de niveaux MountyCrawl — partage les globales de game.js (chargé avant). */
|
|
|
|
"use strict";
|
|
|
|
const ED = {
|
|
grid: null, // tableau 2D de caractères : '#' mur, '.' sol, '>' sortie
|
|
start: null, // {x, y}
|
|
monsters: [], // specs {x, y, type, tpl} ou {x, y, boss: true}
|
|
items: [], // specs {x, y, kind, idx?, gold?}
|
|
doors: [], // {x, y, target: id de niveau publié}
|
|
editing: null, // {id, secret} quand on modifie un niveau déjà publié
|
|
brush: { mode: "tile", char: "#" },
|
|
painting: false,
|
|
};
|
|
|
|
function edReset() {
|
|
ED.grid = [];
|
|
for (let y = 0; y < MAP_H; y++) {
|
|
ED.grid.push([]);
|
|
for (let x = 0; x < MAP_W; x++) {
|
|
const border = x === 0 || y === 0 || x === MAP_W - 1 || y === MAP_H - 1;
|
|
ED.grid[y].push(border ? "#" : ".");
|
|
}
|
|
}
|
|
ED.start = null;
|
|
ED.monsters = [];
|
|
ED.items = [];
|
|
ED.doors = [];
|
|
ED.editing = null;
|
|
const name = document.getElementById("ed-name");
|
|
if (name) { name.value = ""; document.getElementById("ed-author").value = ""; }
|
|
edSyncPublishLabel();
|
|
}
|
|
|
|
/* ---------- Mes niveaux (clés d'auteur en localStorage) ---------- */
|
|
|
|
function myLevels() {
|
|
try { return JSON.parse(localStorage.getItem("mc_myLevels")) || []; }
|
|
catch { return []; }
|
|
}
|
|
|
|
function saveMyLevel(entry) {
|
|
const mine = myLevels().filter(l => l.id !== entry.id);
|
|
mine.push(entry);
|
|
localStorage.setItem("mc_myLevels", JSON.stringify(mine));
|
|
}
|
|
|
|
function edRenderMine() {
|
|
const div = document.getElementById("ed-mine");
|
|
div.innerHTML = "";
|
|
const mine = myLevels();
|
|
if (mine.length === 0) {
|
|
div.innerHTML = '<p class="ed-note">Aucun pour l\'instant — publie un niveau et il apparaîtra ici, modifiable à volonté.</p>';
|
|
return;
|
|
}
|
|
for (const entry of mine) {
|
|
const b = document.createElement("button");
|
|
b.textContent = `✏️ ${entry.name}`;
|
|
b.title = "Recharger ce niveau dans l'éditeur pour le modifier";
|
|
b.onclick = () => edLoadMine(entry);
|
|
div.appendChild(b);
|
|
}
|
|
}
|
|
|
|
async function edLoadMine(entry) {
|
|
try {
|
|
const res = await fetch("api/levels/" + entry.id);
|
|
if (!res.ok) { edFlash("Niveau introuvable sur le serveur.", true); return; }
|
|
const level = await res.json();
|
|
ED.grid = level.grid.map(row => row.split(""));
|
|
ED.start = level.start;
|
|
ED.monsters = level.monsters;
|
|
ED.items = level.items;
|
|
ED.doors = level.doors || [];
|
|
ED.editing = { id: entry.id, secret: entry.secret };
|
|
document.getElementById("ed-name").value = level.name;
|
|
document.getElementById("ed-author").value = level.author;
|
|
edSyncPublishLabel();
|
|
edRender();
|
|
edFlash(`« ${level.name} » chargé — modifie puis « Mettre à jour ».`, false);
|
|
} catch {
|
|
edFlash("Impossible de joindre le serveur.", true);
|
|
}
|
|
}
|
|
|
|
function edSyncPublishLabel() {
|
|
const btn = document.getElementById("ed-publish");
|
|
if (btn) btn.textContent = ED.editing ? "💾 Mettre à jour le niveau" : "🌍 Publier en ligne";
|
|
}
|
|
|
|
/* ---------- Palette ---------- */
|
|
|
|
function edBuildPalette() {
|
|
const tiles = [
|
|
{ label: "🟫 Mur", mode: "tile", char: "#" },
|
|
{ label: "⬜ Sol", mode: "tile", char: "." },
|
|
{ label: "▼ Sortie", mode: "tile", char: ">" },
|
|
{ label: "🧌 Départ du Trõll", mode: "start" },
|
|
{ label: "🧽 Gomme (entités)", mode: "erase" },
|
|
];
|
|
const tilesDiv = document.getElementById("ed-tiles");
|
|
tilesDiv.innerHTML = "";
|
|
for (const t of tiles) edAddBrushBtn(tilesDiv, t.label, t);
|
|
|
|
// 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 = "";
|
|
edAddBrushBtn(monstersDiv, "🐲 Poser le monstre choisi", { mode: "monster" });
|
|
|
|
const itemsDiv = document.getElementById("ed-items");
|
|
itemsDiv.innerHTML = "";
|
|
edAddBrushBtn(itemsDiv, "🧪 Potion (aléatoire)", { mode: "item", kind: "potion" });
|
|
edAddBrushBtn(itemsDiv, "📜 Parchemin (aléatoire)", { mode: "item", kind: "scroll" });
|
|
edAddBrushBtn(itemsDiv, "💰 Mountyzédons", { mode: "item", kind: "gold" });
|
|
for (const [slot, info] of Object.entries(GEAR_SLOTS)) {
|
|
edAddBrushBtn(itemsDiv, `${info.emoji} ${info.label} (aléatoire)`, { mode: "item", kind: "gear", slot });
|
|
}
|
|
|
|
const doorsDiv = document.getElementById("ed-doors");
|
|
doorsDiv.innerHTML = "";
|
|
edAddBrushBtn(doorsDiv, "🚪 Placer une porte", { mode: "door" });
|
|
|
|
const toolsDiv = document.getElementById("ed-tools");
|
|
toolsDiv.innerHTML = "";
|
|
const newBtn = document.createElement("button");
|
|
newBtn.textContent = "📄 Nouveau niveau (vider)";
|
|
newBtn.onclick = () => { edReset(); edRender(); };
|
|
toolsDiv.appendChild(newBtn);
|
|
const fill = (char, label) => {
|
|
const b = document.createElement("button");
|
|
b.textContent = label;
|
|
b.onclick = () => {
|
|
for (let y = 1; y < MAP_H - 1; y++)
|
|
for (let x = 1; x < MAP_W - 1; x++) ED.grid[y][x] = char;
|
|
if (char === "#") { ED.start = null; ED.monsters = []; ED.items = []; }
|
|
edRender();
|
|
};
|
|
toolsDiv.appendChild(b);
|
|
};
|
|
fill(".", "⬜ Tout remplir de sol");
|
|
fill("#", "🟫 Tout remplir de mur (vide les entités)");
|
|
const caves = document.createElement("button");
|
|
caves.textContent = "🎲 Caverne aléatoire";
|
|
caves.onclick = () => {
|
|
const cavern = generateCavern(MAP_W, MAP_H);
|
|
for (let y = 0; y < MAP_H; y++)
|
|
for (let x = 0; x < MAP_W; x++)
|
|
ED.grid[y][x] = cavern[y][x] === T_WALL ? "#" : ".";
|
|
edPruneEntities();
|
|
edRender();
|
|
};
|
|
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;
|
|
b.className = "ed-brush";
|
|
b.onclick = () => {
|
|
ED.brush = brush;
|
|
parent.parentElement.querySelectorAll(".ed-brush").forEach(x => x.classList.remove("selected"));
|
|
b.classList.add("selected");
|
|
};
|
|
parent.appendChild(b);
|
|
}
|
|
|
|
/* Supprime les entités qui se retrouvent dans un mur après modification du terrain. */
|
|
function edPruneEntities() {
|
|
const onFloor = e => ED.grid[e.y][e.x] !== "#";
|
|
ED.monsters = ED.monsters.filter(onFloor);
|
|
ED.items = ED.items.filter(onFloor);
|
|
ED.doors = ED.doors.filter(onFloor);
|
|
if (ED.start && !onFloor(ED.start)) ED.start = null;
|
|
}
|
|
|
|
/* Remplit le sélecteur de cible des portes avec les niveaux publiés. */
|
|
async function edRefreshDoorTargets() {
|
|
const sel = document.getElementById("ed-door-target");
|
|
sel.innerHTML = "<option value=''>— choisis le niveau cible —</option>";
|
|
try {
|
|
const levels = await (await fetch("api/levels")).json();
|
|
for (const l of levels) {
|
|
if (ED.editing && l.id === ED.editing.id) continue; // pas de porte vers soi-même
|
|
const opt = document.createElement("option");
|
|
opt.value = l.id;
|
|
opt.textContent = `${l.name} (par ${l.author})`;
|
|
sel.appendChild(opt);
|
|
}
|
|
} catch { /* hors-ligne : le sélecteur reste vide */ }
|
|
}
|
|
|
|
/* ---------- Application du pinceau ---------- */
|
|
|
|
function edApply(x, y) {
|
|
if (x < 0 || y < 0 || x >= MAP_W || y >= MAP_H) return;
|
|
const b = ED.brush;
|
|
if (b.mode === "tile") {
|
|
// bordure inviolable pour garder le troll dans la carte
|
|
const border = x === 0 || y === 0 || x === MAP_W - 1 || y === MAP_H - 1;
|
|
if (border && b.char !== "#") return;
|
|
ED.grid[y][x] = b.char;
|
|
if (b.char === "#") {
|
|
ED.monsters = ED.monsters.filter(m => m.x !== x || m.y !== y);
|
|
ED.items = ED.items.filter(i => i.x !== x || i.y !== y);
|
|
ED.doors = ED.doors.filter(d => d.x !== x || d.y !== y);
|
|
if (ED.start && ED.start.x === x && ED.start.y === y) ED.start = null;
|
|
}
|
|
} else if (b.mode === "start") {
|
|
if (ED.grid[y][x] === "#") return;
|
|
ED.start = { x, y };
|
|
} else if (b.mode === "erase") {
|
|
ED.monsters = ED.monsters.filter(m => m.x !== x || m.y !== y);
|
|
ED.items = ED.items.filter(i => i.x !== x || i.y !== y);
|
|
ED.doors = ED.doors.filter(d => d.x !== x || d.y !== y);
|
|
if (ED.start && ED.start.x === x && ED.start.y === y) ED.start = null;
|
|
} else if (b.mode === "door") {
|
|
if (ED.grid[y][x] === "#") return;
|
|
const target = document.getElementById("ed-door-target").value;
|
|
if (!target) { edFlash("Choisis d'abord le niveau cible de la porte dans la liste.", true); return; }
|
|
ED.doors = ED.doors.filter(d => d.x !== x || d.y !== 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);
|
|
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);
|
|
const spec = { x, y, kind: b.kind };
|
|
if (b.idx !== undefined) spec.idx = b.idx;
|
|
if (b.slot !== undefined) spec.slot = b.slot;
|
|
if (b.kind === "gold") spec.gold = 60;
|
|
ED.items.push(spec);
|
|
}
|
|
edRender();
|
|
}
|
|
|
|
/* ---------- Rendu ---------- */
|
|
|
|
function edRender() {
|
|
const canvas = document.getElementById("ed-map");
|
|
const ctx = canvas.getContext("2d");
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
ctx.font = "18px serif";
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "middle";
|
|
|
|
for (let y = 0; y < MAP_H; y++) {
|
|
for (let x = 0; x < MAP_W; x++) {
|
|
const c = ED.grid[y][x];
|
|
ctx.fillStyle = c === "#" ? "#4a3a22" : "#7a6a45";
|
|
ctx.fillRect(x * TILE, y * TILE, TILE - 1, TILE - 1);
|
|
if (c === ">") {
|
|
ctx.fillStyle = "#111";
|
|
ctx.fillText("▼", x * TILE + TILE / 2, y * TILE + TILE / 2 + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
const disc = (x, y, color) => {
|
|
ctx.fillStyle = color;
|
|
ctx.beginPath();
|
|
ctx.arc(x * TILE + TILE / 2, y * TILE + TILE / 2, TILE / 2 - 3, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
};
|
|
for (const i of ED.items) {
|
|
disc(i.x, i.y, i.kind === "gold" ? "#caa53d" : i.kind === "potion" ? "#5d8535"
|
|
: i.kind === "scroll" ? "#d8c890" : "#7a8db0");
|
|
ctx.fillStyle = "#1a140e";
|
|
const emoji = i.kind === "potion" ? "🧪" : i.kind === "scroll" ? "📜" : i.kind === "gold" ? "💰"
|
|
: i.kind === "gear" ? (GEAR_SLOTS[i.slot] || GEAR_SLOTS.arme).emoji
|
|
: i.kind === "weapon" ? "🗡️" : "🦺";
|
|
ctx.fillText(emoji, i.x * TILE + TILE / 2, i.y * TILE + TILE / 2 + 1);
|
|
}
|
|
for (const d of ED.doors) {
|
|
disc(d.x, d.y, "#a06a28");
|
|
ctx.fillStyle = "#1a140e";
|
|
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.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) {
|
|
disc(ED.start.x, ED.start.y, "#8fbf5a");
|
|
ctx.fillStyle = "#1a140e";
|
|
ctx.fillText("🧌", ED.start.x * TILE + TILE / 2, ED.start.y * TILE + TILE / 2 + 1);
|
|
}
|
|
|
|
const status = document.getElementById("ed-status");
|
|
const missing = [];
|
|
if (!ED.start) missing.push("un point de départ 🧌");
|
|
if (ED.monsters.length === 0 && ED.doors.length === 0 && !ED.grid.some(r => r.includes(">")))
|
|
missing.push("au moins un monstre, une porte ou une sortie");
|
|
const extras = [
|
|
ED.doors.length ? `${ED.doors.length} porte(s)` : "",
|
|
ED.grid.some(r => r.includes(">")) ? "une sortie" : "",
|
|
].filter(Boolean).join(", ");
|
|
status.textContent = missing.length
|
|
? "Il manque : " + missing.join(" et ") + "."
|
|
: `Prêt : ${ED.monsters.length} monstre(s), ${ED.items.length} objet(s)${extras ? ", " + extras : ""}.`;
|
|
status.className = missing.length ? "ed-warn" : "ed-ok";
|
|
}
|
|
|
|
/* ---------- Export / test / publication ---------- */
|
|
|
|
function edToLevel() {
|
|
return {
|
|
name: document.getElementById("ed-name").value.trim(),
|
|
author: document.getElementById("ed-author").value.trim(),
|
|
grid: ED.grid.map(row => row.join("")),
|
|
start: ED.start,
|
|
monsters: ED.monsters,
|
|
items: ED.items,
|
|
doors: ED.doors,
|
|
};
|
|
}
|
|
|
|
function edValidate(level) {
|
|
if (!level.start) return "place un point de départ 🧌";
|
|
if (level.monsters.length === 0 && level.doors.length === 0 && !level.grid.some(r => r.includes(">")))
|
|
return "place au moins un monstre, une porte ou une sortie";
|
|
return null;
|
|
}
|
|
|
|
function edTest() {
|
|
const level = edToLevel();
|
|
const err = edValidate(level);
|
|
if (err) { edFlash(err, true); return; }
|
|
if (!level.name) level.name = "Test sans nom";
|
|
if (!level.author) level.author = "moi";
|
|
window.MC_afterEnd = "editor";
|
|
document.getElementById("screen-editor").classList.add("hidden");
|
|
startGame(level);
|
|
}
|
|
|
|
async function edPublish() {
|
|
const level = edToLevel();
|
|
const err = edValidate(level) ||
|
|
(!level.name ? "donne un nom à ton niveau" : null) ||
|
|
(!level.author ? "indique ton pseudo d'auteur" : null);
|
|
if (err) { edFlash(err, true); return; }
|
|
try {
|
|
const updating = !!ED.editing;
|
|
const res = await fetch(updating ? "api/levels/" + ED.editing.id : "api/levels", {
|
|
method: updating ? "PUT" : "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(updating ? { "X-Level-Secret": ED.editing.secret } : {}),
|
|
},
|
|
body: JSON.stringify(level),
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok) { edFlash("Refusé par le serveur : " + data.error, true); return; }
|
|
if (updating) {
|
|
saveMyLevel({ id: ED.editing.id, secret: ED.editing.secret, name: level.name });
|
|
edFlash(`💾 « ${level.name} » mis à jour pour tous les joueurs.`, false);
|
|
} else {
|
|
ED.editing = { id: data.id, secret: data.secret };
|
|
saveMyLevel({ id: data.id, secret: data.secret, name: level.name });
|
|
edSyncPublishLabel();
|
|
edFlash(`🌍 Publié ! « ${level.name} » est jouable par tous (id ${data.id}).`, false);
|
|
}
|
|
edRenderMine();
|
|
edRefreshDoorTargets();
|
|
} catch {
|
|
edFlash("Impossible de joindre le serveur.", true);
|
|
}
|
|
}
|
|
|
|
function edFlash(msg, isError) {
|
|
const status = document.getElementById("ed-status");
|
|
status.textContent = msg;
|
|
status.className = isError ? "ed-warn" : "ed-ok";
|
|
}
|
|
|
|
/* ---------- Niveaux communautaires ---------- */
|
|
|
|
async function communityShow() {
|
|
document.getElementById("screen-create").classList.add("hidden");
|
|
document.getElementById("screen-community").classList.remove("hidden");
|
|
const list = document.getElementById("level-list");
|
|
list.innerHTML = '<p class="lore">Chargement…</p>';
|
|
try {
|
|
const res = await fetch("api/levels");
|
|
const levels = await res.json();
|
|
list.innerHTML = "";
|
|
if (levels.length === 0) {
|
|
list.innerHTML = '<p class="lore">Aucun niveau publié pour l\'instant. Sois le premier, l\'éditeur t\'attend !</p>';
|
|
return;
|
|
}
|
|
for (const l of levels.slice().reverse()) {
|
|
const row = document.createElement("button");
|
|
row.className = "level-row";
|
|
row.textContent = `⚔️ ${l.name} — par ${l.author} · ${l.monsters} monstre(s)`
|
|
+ (l.doors ? ` · ${l.doors} porte(s)` : "") + ` · joué ${l.plays} fois · ${l.date}`;
|
|
row.onclick = async () => {
|
|
const r = await fetch("api/levels/" + l.id);
|
|
if (!r.ok) return;
|
|
const level = await r.json();
|
|
document.getElementById("screen-community").classList.add("hidden");
|
|
window.MC_afterEnd = null;
|
|
startGame(level);
|
|
};
|
|
list.appendChild(row);
|
|
}
|
|
} catch {
|
|
list.innerHTML = '<p class="lore">Impossible de joindre le serveur (le mode communautaire nécessite le site en ligne).</p>';
|
|
}
|
|
}
|
|
|
|
/* ---------- Branchement ---------- */
|
|
|
|
if (typeof document !== "undefined") {
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
edReset();
|
|
edBuildPalette();
|
|
|
|
document.getElementById("btn-editor").onclick = () => {
|
|
document.getElementById("screen-create").classList.add("hidden");
|
|
document.getElementById("screen-editor").classList.remove("hidden");
|
|
edRefreshDoorTargets();
|
|
edRenderMine();
|
|
edRender();
|
|
};
|
|
document.getElementById("ed-back").onclick = () => {
|
|
document.getElementById("screen-editor").classList.add("hidden");
|
|
document.getElementById("screen-create").classList.remove("hidden");
|
|
};
|
|
document.getElementById("btn-community").onclick = communityShow;
|
|
document.getElementById("btn-comm-back").onclick = () => {
|
|
document.getElementById("screen-community").classList.add("hidden");
|
|
document.getElementById("screen-create").classList.remove("hidden");
|
|
};
|
|
document.getElementById("ed-test").onclick = edTest;
|
|
document.getElementById("ed-publish").onclick = edPublish;
|
|
|
|
const canvas = document.getElementById("ed-map");
|
|
const cellOf = e => {
|
|
const rect = canvas.getBoundingClientRect();
|
|
return {
|
|
x: Math.floor((e.clientX - rect.left) * (canvas.width / rect.width) / TILE),
|
|
y: Math.floor((e.clientY - rect.top) * (canvas.height / rect.height) / TILE),
|
|
};
|
|
};
|
|
canvas.addEventListener("mousedown", e => {
|
|
ED.painting = true;
|
|
const { x, y } = cellOf(e);
|
|
edApply(x, y);
|
|
});
|
|
canvas.addEventListener("mousemove", e => {
|
|
if (!ED.painting || ED.brush.mode !== "tile") return;
|
|
const { x, y } = cellOf(e);
|
|
edApply(x, y);
|
|
});
|
|
document.addEventListener("mouseup", () => { ED.painting = false; });
|
|
|
|
// ?screen=editor | community : accès direct (captures d'écran, raccourcis)
|
|
const params = new URLSearchParams(location.search);
|
|
const screen = params.get("screen");
|
|
if (screen === "editor") document.getElementById("btn-editor").onclick();
|
|
else if (screen === "community") communityShow();
|
|
|
|
// ?level=<id> : lien partageable vers un niveau communautaire
|
|
const levelId = params.get("level");
|
|
if (levelId && /^[a-f0-9]{12}$/.test(levelId)) {
|
|
fetch("api/levels/" + levelId)
|
|
.then(r => r.ok ? r.json() : null)
|
|
.then(level => { if (level) startGame(level); });
|
|
}
|
|
});
|
|
}
|