diff --git a/README.md b/README.md index 3f48139..9d58abb 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,15 @@ non affilié au jeu original de Mountyhall SARL. ## Versions +- **2.13.0** (2026-06-17) — **RM% et MM% de l'équipement en plages.** Les valeurs + existantes deviennent les **maximums** et on ajoute un **minimum** par objet + (`rmPctMin`/`mmPctMin`, réglables côté admin). À la **création d'un objet du + monde partagé**, RM% et MM% sont **tirés au hasard dans leur plage** (comme la + puissance des potions) — ex. min 5 / max 10 → un objet entre 5 et 10. Par + défaut min = max (donc rien ne change tant qu'on n'abaisse pas le min). + L'encyclopédie affiche la plage. *(Le solo garde ses valeurs fixes — la plage + est une propriété de la base de référence, éditable et tirée côté monde + partagé.)* - **2.12.0** (2026-06-17) — **L'éditeur de niveaux pose les monstres du bestiaire.** On choisit la **famille → le monstre → l'âge** (« Xorn [Mineur] », « Nécrochore [Naissant] »…) et on place la créature voulue. L'**ancien Béhémoth diff --git a/admin.html b/admin.html index f340473..10a67a2 100644 --- a/admin.html +++ b/admin.html @@ -210,7 +210,7 @@ function renderOverview(ov) { /* ---------- Tuning : bestiaire, trésors, équipement ---------- */ -const GEAR_LABELS = { att: "ATT phy", attMag: "ATT mag", esq: "ESQ", deg: "DEG phy", degMag: "DEG mag", reg: "REG", arm: "Arm. phy", armMag: "Arm. mag", vue: "VUE", pv: "PV", rmPct: "RM %", mmPct: "MM %" }; +const GEAR_LABELS = { att: "ATT phy", attMag: "ATT mag", esq: "ESQ", deg: "DEG phy", degMag: "DEG mag", reg: "REG", arm: "Arm. phy", armMag: "Arm. mag", vue: "VUE", pv: "PV", rmPct: "RM% max", rmPctMin: "RM% min", mmPct: "MM% max", mmPctMin: "MM% min" }; function tuneInput(cat, id, key, def, cur) { const val = cur !== undefined ? cur : def; diff --git a/db.js b/db.js index dd3e1c6..0575383 100644 --- a/db.js +++ b/db.js @@ -30,7 +30,9 @@ const BESTIARY_TXT_KEYS = ["family", "speed", "capacities", "blason", "gender"]; 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 GEAR_KEYS = ["att", "attMag", "esq", "deg", "degMag", "reg", "arm", "armMag", "vue", "pv", "rmPct", "mmPct"]; +// rmPct/mmPct sont les MAX ; rmPctMin/mmPctMin les MIN → à la création d'un objet +// du monde partagé, RM% et MM% sont tirés au hasard dans leur plage (cf. mp.js). +const GEAR_KEYS = ["att", "attMag", "esq", "deg", "degMag", "reg", "arm", "armMag", "vue", "pv", "rmPct", "rmPctMin", "mmPct", "mmPctMin"]; /* Templates de drop façon MountyHall : des suffixes (« de l'Aigle », « des * Mages »…) ajoutés aléatoirement à l'équipement qui tombe, selon une proba @@ -85,10 +87,14 @@ function vanillaMonsters() { } function vanillaGear() { - return Object.entries(gearLib.GEAR).flatMap(([slot, list]) => list.map(def => ({ - slot, name: def.name, emoji: def.emoji, tier: def.tier, twoHanded: !!def.twoHanded, - ...Object.fromEntries(GEAR_KEYS.map(k => [k, def.mods[k] || 0])), - }))); + return Object.entries(gearLib.GEAR).flatMap(([slot, list]) => list.map(def => { + const row = { slot, name: def.name, emoji: def.emoji, tier: def.tier, twoHanded: !!def.twoHanded }; + for (const k of GEAR_KEYS) row[k] = def.mods[k] || 0; + // par défaut, min = la valeur (pas de plage tant que l'admin ne l'abaisse pas) + row.rmPctMin = def.mods.rmPct || 0; + row.mmPctMin = def.mods.mmPct || 0; + return row; + })); } function vanillaTemplates() { @@ -150,6 +156,11 @@ function init(file = ":memory:") { // Migration : la colonne `gender` du bestiaire a été ajoutée après coup → on // l'ajoute aux bases déjà créées AVANT tout INSERT/UPDATE qui la référence. try { DB.exec("ALTER TABLE bestiary ADD COLUMN gender TEXT"); } catch { /* déjà là */ } + // Migration : RM%/MM% min de l'équipement (les valeurs existantes = max). + try { DB.exec("ALTER TABLE gear ADD COLUMN rmPctMin INTEGER"); } catch {} + try { DB.exec("ALTER TABLE gear ADD COLUMN mmPctMin INTEGER"); } catch {} + DB.exec("UPDATE gear SET rmPctMin = rmPct WHERE rmPctMin IS NULL"); + DB.exec("UPDATE gear SET mmPctMin = mmPct WHERE mmPctMin IS NULL"); const insM = DB.prepare(`INSERT OR IGNORE INTO monsters (name, emoji, boss, isStatic, ${MONSTER_KEYS.join(", ")}) VALUES (?, ?, ?, ?, ${MONSTER_KEYS.map(() => "?").join(", ")})`); diff --git a/js/game.js b/js/game.js index 0669fa8..8ba02fe 100644 --- a/js/game.js +++ b/js/game.js @@ -4,7 +4,7 @@ "use strict"; -const APP_VERSION = "2.12.0"; +const APP_VERSION = "2.13.0"; /* 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 2864a22..142d072 100644 --- a/js/treasures.js +++ b/js/treasures.js @@ -162,6 +162,14 @@ function treasuresRender() { for (const def of (gearSource[slot] || [])) { const fx = []; for (const [key, label, unit] of GEAR_MOD_LABELS) { + // RM%/MM% : affichés en plage [min, max] (tirés au hasard à la création) + if (key === "rmPct" || key === "mmPct") { + const max = def.mods[key] || 0, min = def[key + "Min"] != null ? def[key + "Min"] : max; + if (!max && !min) continue; + const txt = min === max ? `${max > 0 ? "+" : ""}${max}` : `${min} à ${max}`; + fx.push(`${label} ${txt}${unit}`); + continue; + } const v = def.mods[key]; if (!v) continue; const cls = v > 0 ? "tz-fx tz-good" : "tz-fx tz-bad"; @@ -272,6 +280,7 @@ function gearBySlotFromRows(rows) { (bySlot[r.slot] = bySlot[r.slot] || []).push({ emoji: r.emoji, name: r.name, tier: r.tier, twoHanded: !!r.twoHanded, note: staticDef && staticDef.note, mods, + rmPctMin: r.rmPctMin != null ? r.rmPctMin : r.rmPct, mmPctMin: r.mmPctMin != null ? r.mmPctMin : r.mmPct, }); } return bySlot; diff --git a/mp.js b/mp.js index 52a9072..682723c 100644 --- a/mp.js +++ b/mp.js @@ -97,7 +97,16 @@ function applyGearTuning(item) { if (!item || item.kind !== "gear") return item; const row = db.gearRow(item.slot, item.name); if (row) { - item.mods = Object.fromEntries(db.GEAR_KEYS.map(k => [k, row[k] || 0]).filter(([, v]) => v)); + const mods = {}; + for (const k of db.GEAR_KEYS) { + if (k.endsWith("Min") || k === "rmPct" || k === "mmPct") continue; // bornes : traitées à part + if (row[k]) mods[k] = row[k]; + } + // RM% et MM% : tirés au hasard dans leur plage [min, max] à la création. + const rr = (mn, mx) => { const lo = Math.min(mn || 0, mx || 0), hi = Math.max(mn || 0, mx || 0); return lo + Math.floor(Math.random() * (hi - lo + 1)); }; + const rm = rr(row.rmPctMin, row.rmPct); if (rm) mods.rmPct = rm; + const mm = rr(row.mmPctMin, row.mmPct); if (mm) mods.mmPct = mm; + item.mods = mods; } return item; }