v2.13.0 : RM%/MM% d'equipement en plages (min/max) tirees au drop

db.js : GEAR_KEYS += rmPctMin, mmPctMin (les rmPct/mmPct existants = max) ;
vanillaGear : min defaut = la valeur (pas de plage) ; migration ALTER gear
ADD COLUMN rmPctMin/mmPctMin + UPDATE min=valeur. mp.js applyGearTuning :
mods construits hors cles Min, RM%/MM% tires au hasard dans [min,max] a la
creation (comme les potions). treasures.js : encyclopedie affiche la plage
'min a max %' (rmPctMin/mmPctMin portes sur le def). admin GEAR_LABELS :
RM%/MM% min + max. Solo : valeurs fixes (la plage vit dans la base / monde
partage).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
perco
2026-06-17 11:35:43 +02:00
co-authored by Claude Opus 4.8
parent dc3685de11
commit f31ac75e1d
6 changed files with 46 additions and 8 deletions
+9
View File
@@ -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
+1 -1
View File
@@ -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;
+16 -5
View File
@@ -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(", ")})`);
+1 -1
View File
@@ -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;
+9
View File
@@ -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(`<span class="tz-fx ${max < 0 ? "tz-bad" : "tz-good"}">${label} ${txt}${unit}</span>`);
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;
+10 -1
View File
@@ -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;
}