- js/scrolls.js : les 7 parchemins standards de Mountypedia (X 1-5), effets via le système d'effets magiques, un seul jet de D3 par parchemin - Effets de zone (rayon 3 cases) : Rune Explosive (dégâts aux monstres), Yeu'Ki'Pic (VUE des monstres réduite, statut à durée) - TOUR ±X*30 min converti en ±X PA par tour (PA min. 1) - Drops : coffres (12 %), butin de monstres (25 %), pinceau éditeur, validation serveur du kind « scroll » - js/treasures.js : page encyclopédie des 25 potions et 7 parchemins (niveau X, durée, formules), boutons accueil + en jeu Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
172 lines
5.8 KiB
JavaScript
172 lines
5.8 KiB
JavaScript
/* Parchemins MountyHall — trésors à lire, effets et durée en tours (1 tour = 1 DLA).
|
||
* Source : https://mountypedia.mountyhall.com/Mountyhall/Parchemins
|
||
* Convention temps → PA : −30 min de TOUR = +1 PA au début du tour (comme le
|
||
* Jus de Chronomètre) ; −15 min = +1 PA par tranche de 2 niveaux (arrondi sup.).
|
||
* Les effets de zone touchent le lecteur et tout monstre dans un rayon de
|
||
* SCROLL_ZONE_RADIUS cases. */
|
||
|
||
"use strict";
|
||
|
||
/* potions.js est chargé avant ce fichier dans le navigateur ; en node, require explicite. */
|
||
if (typeof module !== "undefined" && module.exports && typeof sharedMod === "undefined") {
|
||
Object.assign(globalThis, require("./potions.js"));
|
||
}
|
||
|
||
const SCROLL_ZONE_RADIUS = 3;
|
||
|
||
const SCROLL_IDS = [
|
||
"runeCyclopes", "clairvoyance", "ideesConfuses", "runeExplosive",
|
||
"planGenial", "yeuKiPic", "runeFoins",
|
||
];
|
||
|
||
function scrollRand(min, max) {
|
||
return min + Math.floor(Math.random() * (max - min + 1));
|
||
}
|
||
|
||
const SCROLL_DEFS = {
|
||
runeCyclopes: {
|
||
name: "Rune des Cyclopes", emoji: "👁️", color: "#c89048",
|
||
duration: 4,
|
||
build(_t, p, rollDice) {
|
||
const attR = sharedMod(rollDice(p, 3).total, p, 3, false);
|
||
const lines = [`ATT ${attR.line}`, `DEG +${p}`, `VUE −${p}`];
|
||
return {
|
||
log: `Rune des Cyclopes : ${lines.join(", ")} pendant 4 tours.`,
|
||
effects: [makeEffect("Rune des Cyclopes", "👁️", 4, {
|
||
attFlat: attR.value, degFlat: p, vue: -p,
|
||
}, lines)],
|
||
};
|
||
},
|
||
},
|
||
clairvoyance: {
|
||
name: "Traité de Clairvoyance", emoji: "🔮", color: "#88a8e0",
|
||
duration: 4,
|
||
build(_t, p) {
|
||
const lines = [`VUE +${p}`, `DLA +${p} PA/tour`];
|
||
return {
|
||
log: `Traité de Clairvoyance : VUE +${p}, TOUR accéléré (+${p} PA) pendant 4 tours.`,
|
||
effects: [makeEffect("Traité de Clairvoyance", "🔮", 4, {
|
||
vue: p, dlaBonusPA: p,
|
||
}, lines)],
|
||
};
|
||
},
|
||
},
|
||
ideesConfuses: {
|
||
name: "Idées Confuses", emoji: "💫", color: "#b078b0",
|
||
duration: 3,
|
||
build(_t, p, rollDice) {
|
||
const attR = sharedMod(rollDice(p, 3).total, p, 3, true);
|
||
const lines = [`ATT ${attR.line}`, `DLA −${p} PA/tour`];
|
||
return {
|
||
log: `Idées Confuses : ATT ${attR.line}, TOUR ralenti (−${p} PA) pendant 3 tours.`,
|
||
effects: [makeEffect("Idées Confuses", "💫", 3, {
|
||
attFlat: attR.value, dlaBonusPA: -p,
|
||
}, lines)],
|
||
};
|
||
},
|
||
},
|
||
runeExplosive: {
|
||
name: "Rune Explosive", emoji: "💥", color: "#e06030",
|
||
duration: 0, zone: true,
|
||
build(troll, p, rollDice) {
|
||
const dmg = rollDice(2 * p, 3).total;
|
||
troll.pv = Math.max(1, troll.pv - dmg);
|
||
return {
|
||
log: `Rune Explosive : −${dmg} PV (${2 * p}D3), l'explosion ravage la zone !`,
|
||
effects: [],
|
||
zone: { type: "damage", total: dmg, label: `${2 * p}D3` },
|
||
};
|
||
},
|
||
},
|
||
planGenial: {
|
||
name: "Plan Génial", emoji: "💡", color: "#e0c048",
|
||
duration: 3,
|
||
build(_t, p, rollDice) {
|
||
const pa = Math.ceil(p / 2);
|
||
const attR = sharedMod(rollDice(p, 3).total, p, 3, false);
|
||
const lines = [`ATT ${attR.line}`, `DEG +${p}`, `DLA +${pa} PA/tour`];
|
||
return {
|
||
log: `Plan Génial : ${lines.join(", ")} pendant 3 tours.`,
|
||
effects: [makeEffect("Plan Génial", "💡", 3, {
|
||
attFlat: attR.value, degFlat: p, dlaBonusPA: pa,
|
||
}, lines)],
|
||
};
|
||
},
|
||
},
|
||
yeuKiPic: {
|
||
name: "Yeu'Ki'Pic", emoji: "🌶️", color: "#c04040",
|
||
duration: 2, zone: true,
|
||
build(_t, p) {
|
||
const malus = 3 * p;
|
||
return {
|
||
log: `Yeu'Ki'Pic : un nuage piquant ! VUE −${malus} pour toute la zone pendant 2 tours.`,
|
||
effects: [makeEffect("Yeu'Ki'Pic", "🌶️", 2, { vue: -malus })],
|
||
zone: { type: "vue", malus, turns: 2 },
|
||
};
|
||
},
|
||
},
|
||
runeFoins: {
|
||
name: "Rune des Foins", emoji: "🌾", color: "#a8a048",
|
||
duration: 3,
|
||
build(troll, p, rollDice) {
|
||
// « PV : -2/4 D3 » (Mountypedia) : interprété comme 2D3 ou 4D3 au hasard
|
||
const dice = Math.random() < 0.5 ? 2 : 4;
|
||
const dmg = rollDice(dice, 3).total;
|
||
troll.pv = Math.max(1, troll.pv - dmg);
|
||
const lines = [`DEG −${p}`, "VUE −1"];
|
||
return {
|
||
log: `Rune des Foins : atchoum ! −${dmg} PV (${dice}D3), ${lines.join(", ")} pendant 3 tours.`,
|
||
effects: [makeEffect("Rune des Foins", "🌾", 3, { degFlat: -p, vue: -1 }, lines)],
|
||
};
|
||
},
|
||
},
|
||
};
|
||
|
||
function formatScrollItem(scrollId, power) {
|
||
const def = SCROLL_DEFS[scrollId];
|
||
if (!def) return null;
|
||
return {
|
||
kind: "scroll",
|
||
scrollId,
|
||
power,
|
||
name: `${def.name} (niv. ${power})`,
|
||
emoji: def.emoji,
|
||
color: def.color,
|
||
};
|
||
}
|
||
|
||
function makeRandomScroll() {
|
||
const id = SCROLL_IDS[Math.floor(Math.random() * SCROLL_IDS.length)];
|
||
return formatScrollItem(id, scrollRand(1, 5));
|
||
}
|
||
|
||
function makeScrollItem(scrollId, power) {
|
||
if (!SCROLL_DEFS[scrollId]) return makeRandomScroll();
|
||
return formatScrollItem(scrollId, power != null ? power : scrollRand(1, 5));
|
||
}
|
||
|
||
/* Lit un parchemin. Retourne null si inconnu, sinon { zone } — game.js applique
|
||
* l'éventuel effet de zone aux monstres alentour. */
|
||
function readScroll(troll, item, rollDice, logFn) {
|
||
const def = SCROLL_DEFS[item.scrollId];
|
||
if (!def) {
|
||
logFn("Ce parchemin est illisible.", "info");
|
||
return null;
|
||
}
|
||
const p = item.power != null ? item.power : scrollRand(1, 5);
|
||
const result = def.build(troll, p, rollDice);
|
||
if (result.effects.length) {
|
||
troll.potionEffects = troll.potionEffects || [];
|
||
troll.potionEffects.push(...result.effects);
|
||
}
|
||
logFn(`📜 ${result.log}`, "good");
|
||
return { zone: result.zone || null };
|
||
}
|
||
|
||
if (typeof module !== "undefined" && module.exports) {
|
||
module.exports = {
|
||
SCROLL_IDS, SCROLL_DEFS, SCROLL_ZONE_RADIUS,
|
||
formatScrollItem, makeRandomScroll, makeScrollItem, readScroll,
|
||
};
|
||
}
|