Files
mountycrawl/js/potions.js
T
2026-06-11 17:26:29 +02:00

525 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Potions MountyHall — effets et durée en tours (1 tour = 1 DLA passée).
* Source : https://mountypedia.mountyhall.com/Mountyhall/Potion */
"use strict";
const POTION_IDS = [
"biskot", "doverPowa", "bonneBouffe", "corruption", "fertilite", "feu", "longueVue",
"kouleMann", "djhinTonik", "glacier", "calvok", "rhume", "grippe", "pneumonie",
"cervelle", "chronometre", "metomol", "guerison", "painture", "pufPuff", "sangToh",
"sinneKhole", "toxine", "voiputrin", "zetCrak",
];
function potRand(min, max) {
return min + Math.floor(Math.random() * (max - min + 1));
}
function potPick(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function corruptionYZ(x) {
if (x <= 3) return { y: 0, z: 0 };
if (x <= 5) return { y: potRand(5, 10), z: potRand(5, 10) };
if (x === 6) return { y: potRand(11, 20), z: potRand(11, 20) };
return { y: potRand(16, 30), z: potRand(16, 30) };
}
/* Fabrique un effet actif (durée en tours restants). */
function makeEffect(name, emoji, turnsLeft, mods = {}) {
return { name, emoji, turnsLeft, ...mods };
}
const POTION_DEFS = {
biskot: {
name: "Biskot", emoji: "🍪", color: "#c4a574",
duration: 2,
rollPower: () => 0,
build(troll, _p, rollDice) {
const heal = rollDice(2, 3).total;
troll.pv = Math.min(troll.pvMax, troll.pv + heal);
return {
log: `Biskot : +2 REG et +${heal} PV (${heal} = 2D3) pendant 2 tours.`,
effects: [makeEffect("Biskot", "🍪", 2, { reg: 2, biskot: true })],
};
},
},
doverPowa: {
name: "Dover Powa", emoji: "⚡", color: "#6eb5ff",
duration: 2,
rollPower: () => potRand(11, 100),
build(troll, p) {
const yy = potRand(11, 100);
return {
log: `Dover Powa : MM +${yy} %, RM ${p} % pendant 2 tours.`,
effects: [makeEffect("Dover Powa", "⚡", 2, { mmPct: yy, rmPct: -p })],
};
},
},
bonneBouffe: {
name: "Elixir de Bonne Bouffe", emoji: "🍖", color: "#d47848",
duration: 5,
rollPower: () => potRand(3, 7),
build(_t, p) {
return {
log: `Elixir de Bonne Bouffe : DEG +${p}, REG +${p} pendant 5 tours.`,
effects: [makeEffect("Elixir de Bonne Bouffe", "🍖", 5, { deg: p, reg: p })],
};
},
},
corruption: {
name: "Elixir de Corruption", emoji: "☠️", color: "#4a2858",
duration: 5,
rollPower: () => potRand(3, 7),
build(_t, p) {
const { y, z } = corruptionYZ(p);
return {
log: `Elixir de Corruption : malus multiples (X=${p}, RM ${y} %, MM ${z} %) pendant 5 tours.`,
effects: [makeEffect("Elixir de Corruption", "☠️", 5, {
att: p, esq: -p, deg: p, reg: -p, vue: -p, armor: p, rmPct: -y, mmPct: -z,
})],
};
},
},
fertilite: {
name: "Elixir de Fertilité", emoji: "🌱", color: "#5d9a48",
duration: 5,
rollPower: () => potRand(3, 7),
build(_t, p) {
return {
log: `Elixir de Fertilité : ATT +${p}D6, DEG +${p} pendant 5 tours.`,
effects: [makeEffect("Elixir de Fertilité", "🌱", 5, { att: p, deg: p })],
};
},
},
feu: {
name: "Elixir de Feu", emoji: "🔥", color: "#e85830",
duration: 5,
rollPower: () => potRand(3, 7),
build(_t, p) {
return {
log: `Elixir de Feu : ESQ +${p}D6, VUE +${p} pendant 5 tours.`,
effects: [makeEffect("Elixir de Feu", "🔥", 5, { esq: p, vue: p })],
};
},
},
longueVue: {
name: "Elixir de Longue-Vue", emoji: "🔭", color: "#88a8c8",
duration: 3,
rollPower: () => potPick([1, 2, 3, 5, 8]),
build(_t, p) {
return {
log: `Elixir de Longue-Vue : VUE +${p} pendant 3 tours.`,
effects: [makeEffect("Elixir de Longue-Vue", "🔭", 3, { vue: p })],
};
},
},
kouleMann: {
name: "Essence de KouleMann", emoji: "🎵", color: "#9a78c8",
duration: 4,
rollPower: () => potRand(1, 5),
build(troll, p, rollDice) {
const dice = Math.max(1, Math.floor(p / 2));
const heal = rollDice(dice, 3).total;
troll.pv = Math.min(troll.pvMax, troll.pv + heal);
return {
log: `Essence de KouleMann : REG +${p}, VUE +${p}, +${heal} PV (${dice}D3) pendant 4 tours.`,
effects: [makeEffect("Essence de KouleMann", "🎵", 4, { reg: p, vue: p })],
};
},
},
djhinTonik: {
name: "Extrait de DjhinTonik", emoji: "🧞", color: "#48a8a8",
duration: 4,
rollPower: () => potRand(1, 5),
build(troll, p, rollDice) {
const heal = rollDice(2, 3).total;
troll.pv = Math.min(troll.pvMax, troll.pv + heal);
return {
log: `Extrait de DjhinTonik : DEG +${p}, REG +${p}, +${heal} PV (2D3) pendant 4 tours.`,
effects: [makeEffect("Extrait de DjhinTonik", "🧞", 4, { deg: p, reg: p })],
};
},
},
glacier: {
name: "Extrait du Glacier", emoji: "🧊", color: "#a8d8f0",
duration: 5,
rollPower: () => potRand(3, 7),
build(_t, p) {
return {
log: `Extrait du Glacier : REG +${p}, Armure +${p} pendant 5 tours.`,
effects: [makeEffect("Extrait du Glacier", "🧊", 5, { reg: p, armor: p })],
};
},
},
calvok: {
name: "Fiole de Calvok", emoji: "🥃", color: "#d8c090",
duration: 1,
rollPower: () => potRand(2, 6),
build(_t, p) {
const malus = p * 5;
return {
log: `Fiole de Calvok : Concentration ${malus} % pendant 1 tour.`,
effects: [makeEffect("Fiole de Calvok", "🥃", 1, { concentrationPct: -malus })],
};
},
},
rhume: {
name: "Rhume en Conserve", emoji: "🤧", color: "#c8d8a8",
duration: 3,
rollPower: () => potRand(1, 2),
build(_t, p) {
return {
log: `Rhume en Conserve : ATT/ESQ/DEG/REG ${p} pendant 3 tours.`,
effects: [makeEffect("Rhume en Conserve", "🤧", 3, { att: -p, esq: -p, deg: -p, reg: -p })],
};
},
},
grippe: {
name: "Grippe en Conserve", emoji: "🤒", color: "#d8a848",
duration: 3,
rollPower: () => potRand(3, 4),
build(_t, p) {
return {
log: `Grippe en Conserve : ATT/ESQ/DEG/REG ${p} pendant 3 tours.`,
effects: [makeEffect("Grippe en Conserve", "🤒", 3, { att: -p, esq: -p, deg: -p, reg: -p })],
};
},
},
pneumonie: {
name: "Pneumonie en Conserve", emoji: "😷", color: "#a87878",
duration: 3,
rollPower: () => 5,
build(_t, _p) {
return {
log: "Pneumonie en Conserve : ATT/ESQ/DEG/REG 5 pendant 3 tours.",
effects: [makeEffect("Pneumonie en Conserve", "😷", 3, { att: -5, esq: -5, deg: -5, reg: -5 })],
};
},
},
cervelle: {
name: "Jus de Cervelle", emoji: "🧠", color: "#e8a8c8",
duration: 1,
rollPower: () => potRand(2, 6),
build(_t, p) {
const bonus = p * 5;
return {
log: `Jus de Cervelle : Concentration +${bonus} % pendant 1 tour.`,
effects: [makeEffect("Jus de Cervelle", "🧠", 1, { concentrationPct: bonus })],
};
},
},
chronometre: {
name: "Jus de Chronomètre", emoji: "⏱️", color: "#c8c848",
duration: 3,
rollPower: () => potRand(1, 5),
build(_t, p) {
return {
log: `Jus de Chronomètre : DLA accélérée (+${p} PA en début de tour) pendant 3 tours.`,
effects: [makeEffect("Jus de Chronomètre", "⏱️", 3, { dlaBonusPA: p })],
};
},
},
metomol: {
name: "Métomol", emoji: "💊", color: "#a8a8d8",
duration: 2,
rollPower: () => potRand(1, 5),
build(_t, p) {
return {
log: `Métomol : DLA accélérée (+${p} PA) et Armure ${2 * p} pendant 2 tours.`,
effects: [makeEffect("Métomol", "💊", 2, { dlaBonusPA: p, armor: -2 * p })],
};
},
},
guerison: {
name: "Potion de Guérison", emoji: "🧪", color: "#5d8535",
duration: 0,
rollPower: () => potRand(1, 5),
build(troll, p, rollDice) {
const heal = rollDice(2 * p, 3).total;
troll.pv = Math.min(troll.pvMax, troll.pv + heal);
return { log: `Potion de Guérison : +${heal} PV (${2 * p}D3).`, effects: [] };
},
},
painture: {
name: "Potion de Pàïntûré", emoji: "🎨", color: "#e878a8",
duration: 0,
rollPower: () => potRand(1, 5),
build(troll, p) {
troll.camo = false;
troll.blockCamoTurns = Math.max(troll.blockCamoTurns || 0, p);
return {
log: `Potion de Pàïntûré : visible et impossible de se camoufler pendant ${p} tour(s).`,
effects: [makeEffect("Potion de Pàïntûré", "🎨", p, { blockCamo: true })],
};
},
},
pufPuff: {
name: "PufPuff", emoji: "💨", color: "#b8b8b8",
duration: 3,
rollPower: () => potRand(0, 2),
build(troll, p, rollDice) {
const y = p >= 2 ? rollDice(2, 3).total : 0;
if (y > 0) troll.pv = Math.max(1, troll.pv - y);
return {
log: `PufPuff : ATT/ESQ ${p}D6, VUE ${p + 1}${y ? `, ${y} PV` : ""} pendant 3 tours.`,
effects: [makeEffect("PufPuff", "💨", 3, { att: -p, esq: -p, vue: -(p + 1) })],
};
},
},
sangToh: {
name: "Sang de Toh Réroh", emoji: "🩸", color: "#a83030",
duration: 4,
rollPower: () => potRand(1, 5),
build(_t, p) {
return {
log: `Sang de Toh Réroh : ATT +${p}D6, ESQ +${p}D6, VUE +${p} pendant 4 tours.`,
effects: [makeEffect("Sang de Toh Réroh", "🩸", 4, { att: p, esq: p, vue: p })],
};
},
},
sinneKhole: {
name: "Sinne Khole", emoji: "🕳️", color: "#383838",
duration: 2,
rollPower: () => potRand(11, 100),
build(_t, p) {
const yy = potRand(11, 100);
return {
log: `Sinne Khole : RM +${p} %, MM ${yy} % pendant 2 tours.`,
effects: [makeEffect("Sinne Khole", "🕳️", 2, { rmPct: p, mmPct: -yy })],
};
},
},
toxine: {
name: "Toxine Violente", emoji: "☣️", color: "#78c848",
duration: 0,
rollPower: () => potRand(1, 5),
build(troll, p, rollDice) {
const dmg = rollDice(2 * p, 3).total;
troll.pv = Math.max(1, troll.pv - dmg);
return { log: `Toxine Violente : ${dmg} PV (${2 * p}D3).`, effects: [] };
},
},
voiputrin: {
name: "Voï'Pu'Rin", emoji: "🌫️", color: "#686868",
duration: 2,
rollPower: () => potRand(1, 5),
build(_t, p) {
const malus = 10 * p;
return {
log: `Voï'Pu'Rin : VUE ${malus} pendant 2 tours.`,
effects: [makeEffect("Voï'Pu'Rin", "🌫️", 2, { vue: -malus })],
};
},
},
zetCrak: {
name: "Zet Crakdedand", emoji: "🦷", color: "#d8d0a0",
duration: 3,
rollPower: () => potRand(1, 5),
build(troll, p, rollDice) {
const heal = rollDice(p, 3).total;
troll.pv = Math.min(troll.pvMax, troll.pv + heal);
return {
log: `Zet Crakdedand : ATT/ESQ/VUE ${p}, +${heal} PV (${p}D3) pendant 3 tours.`,
effects: [makeEffect("Zet Crakdedand", "🦷", 3, { att: -p, esq: -p, vue: -p })],
};
},
},
};
function sumPotionMods(effects) {
const m = {
att: 0, esq: 0, deg: 0, reg: 0, vue: 0, armor: 0,
mmPct: 0, rmPct: 0, concentrationPct: 0, dlaBonusPA: 0,
};
for (const e of effects || []) {
for (const k of Object.keys(m)) {
if (e[k]) m[k] += e[k];
}
}
return m;
}
function effTroll(troll) {
const m = sumPotionMods(troll.potionEffects);
return {
att: Math.max(1, troll.att + m.att),
esq: Math.max(1, troll.esq + m.esq),
deg: Math.max(1, troll.deg + m.deg),
reg: Math.max(1, troll.reg + m.reg),
vue: Math.max(1, troll.vue + m.vue),
armor: Math.max(0, troll.armor + m.armor),
armorDice: troll.armorDice,
degBonus: troll.degBonus,
pvMax: troll.pvMax,
mmPct: m.mmPct,
rmPct: m.rmPct,
concentrationPct: m.concentrationPct,
dlaBonusPA: m.dlaBonusPA,
};
}
function formatPotionItem(potionId, power) {
const def = POTION_DEFS[potionId];
if (!def) return null;
const suffix = def.duration === 0 && (potionId === "guerison" || potionId === "toxine")
? ` (${2 * power}D3)` : ` (niv. ${power})`;
return {
kind: "potion",
potionId,
power,
name: def.name + suffix,
emoji: def.emoji,
color: def.color,
};
}
function makeRandomPotion() {
const id = POTION_IDS[Math.floor(Math.random() * POTION_IDS.length)];
const def = POTION_DEFS[id];
return formatPotionItem(id, def.rollPower());
}
function makePotionItem(potionId, power) {
const def = POTION_DEFS[potionId];
if (!def) return makeRandomPotion();
return formatPotionItem(potionId, power != null ? power : def.rollPower());
}
function drinkPotion(troll, item, rollDice, logFn) {
const def = POTION_DEFS[item.potionId];
if (!def) {
logFn("Cette fiole est vide ou inconnue.", "info");
return false;
}
const p = item.power != null ? item.power : def.rollPower();
const result = def.build(troll, p, rollDice);
if (result.effects.length) {
troll.potionEffects = troll.potionEffects || [];
troll.potionEffects.push(...result.effects);
}
logFn(result.log, result.effects.length ? "good" : "good");
return true;
}
/* Fin de tour (DLA passée) : décrémente les effets, retire les expirés. */
function tickPotionTurns(troll, logFn) {
troll.tour = (troll.tour || 1) + 1;
if (troll.blockCamoTurns > 0) troll.blockCamoTurns--;
const expired = [];
troll.potionEffects = (troll.potionEffects || []).filter(e => {
if (e.blockCamo) return troll.blockCamoTurns > 0;
e.turnsLeft -= 1;
if (e.turnsLeft <= 0) {
expired.push(e.name);
return false;
}
return true;
});
for (const name of expired) {
logFn(`L'effet de ${name} s'est dissipé.`, "info");
}
return sumPotionMods(troll.potionEffects).dlaBonusPA;
}
const EFFECT_MOD_LABELS = {
att: ["ATT", "D6"], esq: ["ESQ", "D6"], deg: ["DEG", "D3"], reg: ["REG", "D3"],
vue: ["VUE", ""], armor: ["Armure", ""],
};
function formatEffectMods(effect) {
const parts = [];
for (const [key, [label, suffix]] of Object.entries(EFFECT_MOD_LABELS)) {
const v = effect[key];
if (!v) continue;
parts.push(`${label} ${v > 0 ? "+" : ""}${v}${suffix}`);
}
if (effect.mmPct) parts.push(`MM ${effect.mmPct > 0 ? "+" : ""}${effect.mmPct} %`);
if (effect.rmPct) parts.push(`RM ${effect.rmPct > 0 ? "+" : ""}${effect.rmPct} %`);
if (effect.concentrationPct) parts.push(`Concentration ${effect.concentrationPct > 0 ? "+" : ""}${effect.concentrationPct} %`);
if (effect.dlaBonusPA) parts.push(`DLA +${effect.dlaBonusPA} PA/tour`);
if (effect.blockCamo) parts.push("Visible — camouflage impossible");
return parts;
}
function countActiveEffects(troll) {
let n = (troll.potionEffects || []).length;
if (troll.blockCamoTurns > 0 && !(troll.potionEffects || []).some(e => e.blockCamo)) n += 1;
return n;
}
function describeActiveEffects(troll) {
const lines = [];
if (troll.blockCamoTurns > 0) {
lines.push(`🎨 Pàïntûré (${troll.blockCamoTurns} tour(s))`);
}
for (const e of troll.potionEffects || []) {
if (e.blockCamo) continue;
lines.push(`${e.emoji} ${e.name} (${e.turnsLeft} tour(s))`);
}
return lines;
}
function renderEffectsPanel(troll) {
const effects = [...(troll.potionEffects || [])];
const cards = [];
if (troll.blockCamoTurns > 0 && !effects.some(e => e.blockCamo)) {
cards.push({
emoji: "🎨", name: "Pàïntûré", turnsLeft: troll.blockCamoTurns,
mods: ["Visible — camouflage impossible"],
positive: false,
});
}
for (const e of effects) {
const mods = formatEffectMods(e);
const positive = mods.some(m => m.includes("+") && !m.includes(""));
cards.push({
emoji: e.emoji, name: e.name,
turnsLeft: e.blockCamo ? troll.blockCamoTurns : e.turnsLeft,
mods, positive: mods.length ? positive : null,
});
}
if (!cards.length) {
return '<p class="fx-empty">Aucun bonus ni malus magique actif.</p>';
}
const total = sumPotionMods(troll.potionEffects);
const totalLines = formatEffectMods(total);
if (troll.blockCamoTurns > 0) totalLines.push("Camouflage bloqué");
let html = "";
if (totalLines.length) {
html += `<div class="fx-total"><div class="fx-total-title">Total des modificateurs</div>`;
html += totalLines.map(m => `<span class="fx-mod">${m}</span>`).join("");
html += "</div>";
}
for (const c of cards) {
const cls = c.positive === true ? "fx-card-buff" : c.positive === false ? "fx-card-debuff" : "fx-card";
html += `<div class="fx-card ${cls}">`;
html += `<div class="fx-card-head"><span>${c.emoji} ${c.name}</span><span class="fx-turns">${c.turnsLeft} tour(s)</span></div>`;
if (c.mods.length) {
html += `<div class="fx-card-mods">${c.mods.map(m => `<span class="fx-mod">${m}</span>`).join("")}</div>`;
}
html += "</div>";
}
return html;
}
function talentPctWithPotions(troll, talent, cap) {
const eff = effTroll(troll);
return Math.max(0, Math.min(cap, talent.pct + eff.concentrationPct));
}
if (typeof module !== "undefined" && module.exports) {
module.exports = {
POTION_IDS, POTION_DEFS, sumPotionMods, effTroll, formatPotionItem,
makeRandomPotion, makePotionItem, drinkPotion, tickPotionTurns,
describeActiveEffects, renderEffectsPanel, countActiveEffects, formatEffectMods,
talentPctWithPotions, corruptionYZ,
};
}