1.7.0 : équipement Mountypedia — 6 emplacements, 55 objets

- js/gear.js : Arme (12 + 3 à deux mains), Armure (13), Casque (9),
  Bouclier (5), Talisman (7), Bottes (6), valeurs de base officielles
- ATT/ESQ/DEG/REG en dés, Armure/VUE/PV fixes, RM/MM en % (spellMM/spellRM)
- equipGear : échange par emplacement, exclusion 2 mains/bouclier,
  PV max dynamique, gearMods intégré à effTroll
- Drops par tier ≤ profondeur + 1, butin éditeur par emplacement,
  rétrocompat des specs weapon/armor des niveaux publiés
- Page Trésors : section Équipement (6 sous-sections)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
perco
2026-06-11 23:56:30 +02:00
co-authored by Claude Fable 5
parent 6039497e72
commit 2f21f867a5
10 changed files with 314 additions and 51 deletions
+60 -38
View File
@@ -4,7 +4,7 @@
"use strict";
const APP_VERSION = "1.6.0";
const APP_VERSION = "1.7.0";
/* Alpha : maîtrise initiale haute pour les tests. Remettre 15 % / 15 % à la v1.0 officielle. */
const START_COMP_PCT = 90;
@@ -17,6 +17,9 @@ if (typeof module !== "undefined" && module.exports && typeof makeRandomPotion =
if (typeof module !== "undefined" && module.exports && typeof makeRandomScroll === "undefined") {
Object.assign(globalThis, require("./scrolls.js"));
}
if (typeof module !== "undefined" && module.exports && typeof makeRandomGear === "undefined") {
Object.assign(globalThis, require("./gear.js"));
}
/* ================= Dés & règles de base ================= */
@@ -201,7 +204,9 @@ function monsterFromSpec(spec) {
return applyTemplate(type, tpl, spec.x, spec.y);
}
/* Instancie un objet depuis une spec d'éditeur : {x, y, kind, idx?, gold?} */
/* Instancie un objet depuis une spec d'éditeur : {x, y, kind, slot?, idx?, gold?}.
* Les specs « weapon »/« armor » à idx des anciens niveaux publiés sont mappées
* vers l'équipement Mountypedia équivalent. */
function itemFromSpec(spec) {
const base = { x: spec.x, y: spec.y };
if (spec.kind === "potion") return { ...base, ...makePotionItem(spec.potionId, spec.power) };
@@ -210,8 +215,13 @@ function itemFromSpec(spec) {
const gold = spec.gold || 60;
return { ...base, kind: "gold", name: `${gold} Mountyzédons`, emoji: "💰", gold };
}
const list = spec.kind === "weapon" ? WEAPONS : ARMORS;
return { ...base, kind: "gear", ...list[(spec.idx || 0) % list.length] };
if (spec.kind === "gear") return { ...base, ...makeRandomGear(5, spec.slot) };
if (spec.kind === "weapon") {
const name = LEGACY_WEAPON_NAMES[(spec.idx || 0) % LEGACY_WEAPON_NAMES.length];
return { ...base, ...gearItemByName("arme", name) };
}
const name = LEGACY_ARMOR_NAMES[(spec.idx || 0) % LEGACY_ARMOR_NAMES.length];
return { ...base, ...gearItemByName("armure", name) };
}
/* ================= Génération du Monde Souterrain ================= */
@@ -286,30 +296,11 @@ function randomFloor(grid, taken) {
/* ================= Objets ================= */
const WEAPONS = [
{ name: "Dague Rouillée", emoji: "🗡️", slot: "weapon", bonus: 1 },
{ name: "Hache de Jet", emoji: "🪓", slot: "weapon", bonus: 2 },
{ name: "Épée Longue", emoji: "⚔️", slot: "weapon", bonus: 3 },
{ name: "Masse d'Armes", emoji: "🔨", slot: "weapon", bonus: 4 },
];
const ARMORS = [
{ name: "Armure de Cuir", emoji: "🦺", slot: "armor", bonus: 1 },
{ name: "Cotte de Mailles", emoji: "🥋", slot: "armor", bonus: 2 },
{ name: "Armure de Plates", emoji: "🛡️", slot: "armor", bonus: 3 },
];
function makeItem(depth) {
const r = Math.random();
if (r < 0.3) return makeRandomPotion();
if (r < 0.42) return makeRandomScroll();
if (r < 0.58) {
const w = WEAPONS[Math.min(WEAPONS.length - 1, Math.floor(Math.random() * (depth + 1)))];
return { kind: "gear", ...w };
}
if (r < 0.7) {
const a = ARMORS[Math.min(ARMORS.length - 1, Math.floor(Math.random() * depth))];
return { kind: "gear", ...a };
}
if (r < 0.7) return makeRandomGear(depth);
const gold = rollDice(depth, 6).total * 10;
return { kind: "gold", name: `${gold} Mountyzédons`, emoji: "💰", gold };
}
@@ -334,7 +325,8 @@ function newGame(name, race, customLevel = null) {
bought: { att: 0, esq: 0, deg: 0, reg: 0, pv: 0, vue: 0, armor: 0 },
comp: { pct: START_COMP_PCT }, sort: { pct: START_SORT_PCT }, fatigue: 0, compUsed: false,
compPXTurn: false, sortPXTurn: false,
weapon: null, armorItem: null,
equip: { arme: null, armure: null, casque: null, bouclier: null, talisman: null, bottes: null },
gearMods: null,
pa: PA_PER_TURN, pi: 0, totalPI: 0, gold: 0,
bag: [], camo: false, kills: 0, dla: 1, tour: 1,
potionEffects: [], blockCamoTurns: 0,
@@ -610,7 +602,7 @@ function attackMonster(m, opts = {}) {
const cost = opts.cost ?? COSTS.attack;
if (!spendPA(cost)) return false;
cdStart(`⚔️ Attaque sur ${m.name}`);
cdLine(`Vous avez attaqué <b>${m.name}</b> avec votre ${G.troll.weapon ? G.troll.weapon.name : "Grosse Patte de Trõll"}.`);
cdLine(`Vous avez attaqué <b>${m.name}</b> avec votre ${G.troll.equip.arme ? G.troll.equip.arme.name : "Grosse Patte de Trõll"}.`);
const r = resolveAttack(effTroll(G.troll), effMonster(m));
cdAttackRolls(r);
if (r.hit) {
@@ -967,19 +959,43 @@ function useBagItem(idx) {
updateFov();
} else if (item.kind === "gear") {
if (!spendPA(COSTS.equip)) return;
if (item.slot === "weapon") {
if (t.weapon) t.bag.push(t.weapon);
t.weapon = item; t.degBonus = item.bonus;
} else {
if (t.armorItem) t.bag.push(t.armorItem);
t.armorItem = item; t.armor = item.bonus;
}
t.bag.splice(t.bag.indexOf(item), 1);
log(`Tu t'équipes : ${item.emoji} ${item.name} (+${item.bonus}).`, "good");
equipGear(t, item);
}
afterAction();
}
/* Équipe une pièce : range l'ancienne au sac, gère l'exclusion arme à 2 mains /
* bouclier, applique le bonus de PV max et recalcule les modificateurs. */
function equipGear(t, item) {
const unequipTo = (slot) => {
const old = t.equip[slot];
if (!old) return;
t.equip[slot] = null;
t.bag.push(old);
if (old.mods.pv) t.pvMax = Math.max(1, t.pvMax - old.mods.pv);
log(`Tu ranges ${old.emoji} ${old.name} dans ton sac.`, "info");
};
if (item.slot === "arme" && item.twoHanded && t.equip.bouclier) {
unequipTo("bouclier");
log("Une arme à deux mains ne laisse pas de place au bouclier.", "info");
}
if (item.slot === "bouclier" && t.equip.arme && t.equip.arme.twoHanded) {
unequipTo("arme");
log("Impossible de tenir un bouclier avec une arme à deux mains : tu la ranges.", "info");
}
unequipTo(item.slot);
t.equip[item.slot] = item;
if (item.mods.pv) {
t.pvMax = Math.max(1, t.pvMax + item.mods.pv);
t.pv = Math.max(1, Math.min(t.pv, t.pvMax));
}
t.gearMods = gearMods(t.equip);
const fx = formatGearMods(item.mods);
log(`Tu t'équipes : ${item.emoji} ${item.name}${fx ? ` (${fx})` : ""}.`, "good");
if (G) updateFov();
}
/* Effet de zone d'un parchemin : touche tous les monstres à SCROLL_ZONE_RADIUS
* cases ou moins du troll (le lecteur a déjà subi sa part dans readScroll). */
function applyScrollZone(zone) {
@@ -1301,9 +1317,14 @@ function renderPanels() {
if (doorAt(t.x, t.y)) addBtn("🚪 Franchir la porte", enterDoor, true);
addBtn("⏳ Passer la DLA", passDLA, true);
$("equipment").innerHTML =
`<div>Arme : ${t.weapon ? t.weapon.emoji + " " + t.weapon.name + " (+" + t.weapon.bonus + ")" : "—"}</div>` +
`<div>Armure : ${t.armorItem ? t.armorItem.emoji + " " + t.armorItem.name + " (+" + t.armorItem.bonus + ")" : "—"}</div>`;
$("equipment").innerHTML = Object.entries(GEAR_SLOTS).map(([slot, info]) => {
const it = t.equip[slot];
if (!it) return `<div class="eq-line"><span class="eq-slot">${info.label}</span> —</div>`;
const fx = formatGearMods(it.mods);
return `<div class="eq-line"><span class="eq-slot">${info.label}</span> ${it.emoji} ${it.name}` +
`${it.twoHanded ? " <small>(2 mains)</small>" : ""}` +
`${fx ? `<div class="eq-mods">${fx}</div>` : ""}</div>`;
}).join("");
const inv = $("inventory");
inv.innerHTML = "";
@@ -1313,6 +1334,7 @@ function renderPanels() {
const action = item.kind === "potion" ? `boire (${COSTS.potion} PA)`
: item.kind === "scroll" ? `lire (${COSTS.scroll} PA)` : `équiper (${COSTS.equip} PA)`;
b.textContent = `${item.emoji} ${item.name}${action}`;
if (item.kind === "gear") b.title = formatGearMods(item.mods) + (item.twoHanded ? " · 2 mains" : "");
b.disabled = G.over;
b.onclick = () => useBagItem(idx);
inv.appendChild(b);
@@ -1459,7 +1481,7 @@ if (typeof document !== "undefined") {
if (typeof module !== "undefined" && module.exports) {
module.exports = {
APP_VERSION, rollDice, resolveAttack, resolveSpell, masteryRoll, improveCost, levelFromTotalPI, killPX,
RACES, MONSTER_TYPES, BOSS, TEMPLATES, makeMonster, monsterFromSpec, itemFromSpec,
RACES, MONSTER_TYPES, BOSS, TEMPLATES, makeMonster, monsterFromSpec, itemFromSpec, equipGear,
generateCavern, largestRegion,
MAP_W, MAP_H, T_WALL, T_FLOOR, T_STAIRS,
newGame, refreshFov, inSightAt,