1.9.0 : bonus/malus physiques (équipement) vs magiques (potions/parchemins), armure physique et magique distinctes (troll + monstres)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+56
-36
@@ -4,7 +4,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const APP_VERSION = "1.8.0";
|
||||
const APP_VERSION = "1.9.0";
|
||||
|
||||
/* Alpha : maîtrise initiale haute pour les tests. Remettre 15 % / 15 % à la v1.0 officielle. */
|
||||
const START_COMP_PCT = 90;
|
||||
@@ -31,7 +31,10 @@ function rollDice(n, faces) {
|
||||
|
||||
/* Jet d'attaque MountyHall : somme de ATT D6 contre somme de ESQ D6.
|
||||
* Si l'attaque dépasse l'esquive, dégâts = DEG D3 (+ bonus d'arme) − armure.
|
||||
* (Les dégâts sont en D3 comme dans les profils officiels des races.) */
|
||||
* (Les dégâts sont en D3 comme dans les profils officiels des races.)
|
||||
* Armure façon MH : les dégâts PHYSIQUES sont réduits par l'armure totale
|
||||
* (physique + magique) ; les dégâts MAGIQUES (opts.magic) par la seule armure
|
||||
* magique ; opts.ignoreArmor ignore tout (Siphon des Âmes). */
|
||||
function resolveAttack(attacker, defender, opts = {}) {
|
||||
const att = rollDice(attacker.att, 6);
|
||||
const esq = rollDice(defender.esq, 6);
|
||||
@@ -50,9 +53,16 @@ function resolveAttack(attacker, defender, opts = {}) {
|
||||
const deg = rollDice(attacker.deg, 3);
|
||||
const degFlat = attacker.degFlat || 0;
|
||||
result.rawDamage = (deg.total + (attacker.degBonus || 0) + degFlat) * (result.critical ? 2 : 1);
|
||||
// armure : réduction fixe (équipement) + armure naturelle en D3 (achetée en PI)
|
||||
const armor = opts.ignoreArmor ? 0
|
||||
: (defender.armor || 0) + (defender.armorDice ? rollDice(defender.armorDice, 3).total : 0);
|
||||
// armure physique : fixe (base + équipement) + naturelle en D3 (achetée en PI)
|
||||
// armure magique : effets de potions/parchemins (armorMag, peut être négative)
|
||||
let armor = 0;
|
||||
if (!opts.ignoreArmor) {
|
||||
const phys = (defender.armorPhys != null ? defender.armorPhys : defender.armor || 0)
|
||||
+ (defender.armorDice ? rollDice(defender.armorDice, 3).total : 0);
|
||||
const mag = defender.armorMag || 0;
|
||||
armor = Math.max(0, opts.magic ? mag : phys + mag);
|
||||
}
|
||||
result.armorReduction = armor;
|
||||
result.damage = Math.max(1, result.rawDamage - armor);
|
||||
}
|
||||
return result;
|
||||
@@ -126,21 +136,21 @@ const RACES = {
|
||||
desc: "Les tanks du Hall, durs au mal et infatigables.",
|
||||
stats: { att: 3, esq: 3, deg: 3, reg: 1, pvMax: 40, vue: 3 },
|
||||
comp: { name: "Régénération Accrue", cost: 2, desc: "Soigne immédiatement 1D3 par tranche de 15 PV max." },
|
||||
sort: { name: "Rafale Psychique", cost: 4, desc: "Touche automatiquement (imparable) : 1D3 par dé de DEG, ignore l'armure (résistance : moitié)." },
|
||||
sort: { name: "Rafale Psychique", cost: 4, desc: "Touche automatiquement (imparable) : 1D3 par dé de DEG, ignore l'armure physique (résistance : moitié)." },
|
||||
},
|
||||
Kastar: {
|
||||
emoji: "🔴", favored: "deg",
|
||||
desc: "Les vampires du Hall : leurs coups les nourrissent.",
|
||||
stats: { att: 3, esq: 3, deg: 4, reg: 1, pvMax: 30, vue: 3 },
|
||||
comp: { name: "Accélération du Métabolisme", cost: 2, desc: "Sacrifie des PV (1D3 + fatigue) pour regagner 4 PA. La fatigue monte à chaque usage." },
|
||||
sort: { name: "Vampirisme", cost: 4, desc: "Cible adjacente : 2D6 par tranche de 3 dés de DEG vs ESQ, dégâts 1D3 par dé de DEG, ignore l'armure, soigne 50 % (résistance : moitié)." },
|
||||
sort: { name: "Vampirisme", cost: 4, desc: "Cible adjacente : 2D6 par tranche de 3 dés de DEG vs ESQ, dégâts 1D3 par dé de DEG, ignore l'armure physique, soigne 50 % (résistance : moitié)." },
|
||||
},
|
||||
Tomawak: {
|
||||
emoji: "🟡", favored: "vue",
|
||||
desc: "Trõlls furtifs, chasseurs embusqués aux yeux perçants.",
|
||||
stats: { att: 3, esq: 3, deg: 3, reg: 1, pvMax: 30, vue: 4 },
|
||||
comp: { name: "Camouflage", cost: 2, desc: "Invisible aux monstres tant que tu n'attaques pas ; à chaque pas, jet sous 75 % de la maîtrise pour rester caché." },
|
||||
sort: { name: "Projectile Magique", cost: 4, desc: "À distance (portée = Vue) : 1D6 par case de Vue + bonus de proximité, dégâts 1D3 par tranche de 2 cases de Vue, ignore l'armure (résistance : moitié)." },
|
||||
sort: { name: "Projectile Magique", cost: 4, desc: "À distance (portée = Vue) : 1D6 par case de Vue + bonus de proximité, dégâts 1D3 par tranche de 2 cases de Vue, ignore l'armure physique (résistance : moitié)." },
|
||||
},
|
||||
Darkling: {
|
||||
emoji: "🟣", favored: "reg",
|
||||
@@ -153,18 +163,21 @@ const RACES = {
|
||||
|
||||
/* ================= Bestiaire ================= */
|
||||
|
||||
/* Dégâts des monstres en D3, comme ceux des trolls. */
|
||||
/* Dégâts des monstres en D3, comme ceux des trolls.
|
||||
* `armor` = armure physique, `armorMag` = armure magique (réduit seule les
|
||||
* dégâts magiques). Valeurs provisoires à 0 — le bestiaire sera mis à jour
|
||||
* avec les vraies valeurs une fois le système validé. */
|
||||
const MONSTER_TYPES = [
|
||||
{ name: "Gobelin", emoji: "👺", level: 1, att: 2, esq: 2, deg: 2, pv: 12, armor: 0, vue: 4 },
|
||||
{ name: "Champignon Vénéneux", emoji: "🍄", level: 1, att: 3, esq: 1, deg: 3, pv: 10, armor: 0, vue: 1, static: true },
|
||||
{ name: "Araignée Géante", emoji: "🕷️", level: 2, att: 3, esq: 3, deg: 3, pv: 16, armor: 0, vue: 5 },
|
||||
{ name: "Gargouille", emoji: "🦇", level: 3, att: 3, esq: 3, deg: 3, pv: 22, armor: 2, vue: 4 },
|
||||
{ name: "Momie", emoji: "🧟", level: 3, att: 4, esq: 2, deg: 4, pv: 26, armor: 1, vue: 3 },
|
||||
{ name: "Sorcière", emoji: "🧙", level: 4, att: 4, esq: 3, deg: 5, pv: 24, armor: 0, vue: 6 },
|
||||
{ name: "Golem de Pierre", emoji: "🗿", level: 5, att: 4, esq: 1, deg: 6, pv: 40, armor: 4, vue: 3 },
|
||||
{ name: "Gobelin", emoji: "👺", level: 1, att: 2, esq: 2, deg: 2, pv: 12, armor: 0, armorMag: 0, vue: 4 },
|
||||
{ name: "Champignon Vénéneux", emoji: "🍄", level: 1, att: 3, esq: 1, deg: 3, pv: 10, armor: 0, armorMag: 0, vue: 1, static: true },
|
||||
{ name: "Araignée Géante", emoji: "🕷️", level: 2, att: 3, esq: 3, deg: 3, pv: 16, armor: 0, armorMag: 0, vue: 5 },
|
||||
{ name: "Gargouille", emoji: "🦇", level: 3, att: 3, esq: 3, deg: 3, pv: 22, armor: 2, armorMag: 0, vue: 4 },
|
||||
{ name: "Momie", emoji: "🧟", level: 3, att: 4, esq: 2, deg: 4, pv: 26, armor: 1, armorMag: 0, vue: 3 },
|
||||
{ name: "Sorcière", emoji: "🧙", level: 4, att: 4, esq: 3, deg: 5, pv: 24, armor: 0, armorMag: 0, vue: 6 },
|
||||
{ name: "Golem de Pierre", emoji: "🗿", level: 5, att: 4, esq: 1, deg: 6, pv: 40, armor: 4, armorMag: 0, vue: 3 },
|
||||
];
|
||||
|
||||
const BOSS = { name: "Béhémoth", emoji: "👹", level: 9, att: 6, esq: 4, deg: 8, pv: 90, armor: 3, vue: 8, boss: true };
|
||||
const BOSS = { name: "Béhémoth", emoji: "👹", level: 9, att: 6, esq: 4, deg: 8, pv: 90, armor: 3, armorMag: 0, vue: 8, boss: true };
|
||||
|
||||
/* Gabarits d'âge façon MountyHall : plus on descend, plus les bêtes sont vieilles. */
|
||||
const TEMPLATES = [
|
||||
@@ -183,7 +196,7 @@ function applyTemplate(type, tpl, x, y) {
|
||||
esq: Math.max(1, Math.round(type.esq * tpl.mult)),
|
||||
deg: Math.max(1, Math.round(type.deg * tpl.mult)),
|
||||
pv: Math.round(type.pv * tpl.mult), pvMax: Math.round(type.pv * tpl.mult),
|
||||
armor: type.armor, vue: type.vue, static: !!type.static,
|
||||
armor: type.armor, armorMag: type.armorMag || 0, vue: type.vue, static: !!type.static,
|
||||
x, y, boss: false,
|
||||
};
|
||||
}
|
||||
@@ -608,7 +621,7 @@ function attackMonster(m, opts = {}) {
|
||||
if (r.hit) {
|
||||
m.pv -= r.damage;
|
||||
gainPX(1); // +1 PX par attaque réussie (converti en PI)
|
||||
cdLine(`Vous lui avez infligé <span class="cd-val">${r.damage} points de dégâts</span>.`);
|
||||
cdLine(`Vous lui avez infligé <span class="cd-val">${r.damage} points de dégâts</span>${r.armorReduction ? ` (son armure a absorbé ${r.armorReduction} point(s))` : ""}.`);
|
||||
log(`Tu attaques ${m.name} : ATT ${r.attRoll} vs ESQ ${r.esqRoll} → ${r.critical ? "CRITIQUE ! " : "touché ! "}${r.damage} dégâts.`, "combat");
|
||||
if (m.pv <= 0) killMonster(m, "attack");
|
||||
} else {
|
||||
@@ -813,12 +826,13 @@ function useSort() {
|
||||
if (!tryTalent(t.sort, 80, sort.cost, "🔮 Rafale Psychique", "sort")) { cdFlush(); afterAction(); return; }
|
||||
cdLine(`Vous avez attaqué <b>${m.name}</b> grâce à un sortilège.`);
|
||||
cdLine(`Votre jet d'Attaque est : <span class="cd-good">automatiquement réussi</span> (imparable).`);
|
||||
let dmg = rollDice(te.deg, 3).total;
|
||||
const armMag = Math.max(0, effMonster(m).armorMag);
|
||||
let dmg = Math.max(1, rollDice(te.deg, 3).total - armMag);
|
||||
const res = resistInfo(m);
|
||||
if (res) dmg = Math.max(1, Math.ceil(dmg / 2));
|
||||
m.pv -= dmg;
|
||||
cdLine(`Vous lui avez infligé <span class="cd-val">${dmg} points de dégâts</span> (armure ignorée).`);
|
||||
log(`Rafale Psychique : touche automatique, ${dmg} dégâts${res ? " (résisté : moitié)" : ""}, armure ignorée.`, "combat");
|
||||
cdLine(`Vous lui avez infligé <span class="cd-val">${dmg} points de dégâts</span> (armure physique ignorée${armMag ? `, armure magique −${armMag}` : ""}).`);
|
||||
log(`Rafale Psychique : touche automatique, ${dmg} dégâts${res ? " (résisté : moitié)" : ""}, armure physique ignorée.`, "combat");
|
||||
if (m.pv <= 0) killMonster(m, "sort");
|
||||
} else if (t.race === "Kastar") { // Vampirisme : dégâts + vol de vie
|
||||
const m = adjacentMonster();
|
||||
@@ -829,7 +843,7 @@ function useSort() {
|
||||
att: Math.max(1, Math.floor(te.deg * 2 / 3)), deg: te.deg,
|
||||
degBonus: te.degBonus, attFlat: te.attFlat, degFlat: te.degFlat,
|
||||
};
|
||||
const r = resolveAttack(pseudo, effMonster(m), { ignoreArmor: true });
|
||||
const r = resolveAttack(pseudo, effMonster(m), { magic: true });
|
||||
cdAttackRolls(r);
|
||||
if (r.hit) {
|
||||
let dmg = r.damage;
|
||||
@@ -838,7 +852,7 @@ function useSort() {
|
||||
m.pv -= dmg;
|
||||
const heal = Math.ceil(dmg / 2);
|
||||
t.pv = Math.min(t.pvMax, t.pv + heal);
|
||||
cdLine(`Vous lui avez infligé <span class="cd-val">${dmg} points de dégâts</span> (armure ignorée).`);
|
||||
cdLine(`Vous lui avez infligé <span class="cd-val">${dmg} points de dégâts</span> (armure physique ignorée${r.armorReduction ? `, armure magique −${r.armorReduction}` : ""}).`);
|
||||
cdLine(`Vous avez également gagné <span class="cd-good">${heal} points de Vie</span> grâce au Vampirisme.`);
|
||||
log(`Vampirisme : ${dmg} dégâts${res ? " (résisté)" : ""}, tu draines ${heal} PV.`, "good");
|
||||
if (m.pv <= 0) killMonster(m, "sort");
|
||||
@@ -853,14 +867,14 @@ function useSort() {
|
||||
const proxBonus = Math.max(0, te.vue - dist);
|
||||
cdLine(`Vous avez attaqué <b>${m.name}</b> grâce à un sortilège (distance ${dist}, bonus de proximité +${proxBonus}D6).`);
|
||||
const pseudo = { att: te.vue + proxBonus, deg: Math.max(1, Math.floor(te.vue / 2)), degBonus: 0 };
|
||||
const r = resolveAttack(pseudo, effMonster(m), { ignoreArmor: true });
|
||||
const r = resolveAttack(pseudo, effMonster(m), { magic: true });
|
||||
cdAttackRolls(r);
|
||||
if (r.hit) {
|
||||
let dmg = r.damage;
|
||||
const res = resistInfo(m);
|
||||
if (res) dmg = Math.max(1, Math.ceil(dmg / 2));
|
||||
m.pv -= dmg;
|
||||
cdLine(`Vous lui avez infligé <span class="cd-val">${dmg} points de dégâts</span> (armure ignorée).`);
|
||||
cdLine(`Vous lui avez infligé <span class="cd-val">${dmg} points de dégâts</span> (armure physique ignorée${r.armorReduction ? `, armure magique −${r.armorReduction}` : ""}).`);
|
||||
log(`Projectile Magique sur ${m.name} (distance ${dist}) : ${dmg} dégâts${res ? " (résisté)" : ""}.`, "combat");
|
||||
if (m.pv <= 0) killMonster(m, "sort");
|
||||
} else {
|
||||
@@ -907,6 +921,8 @@ function effMonster(m) {
|
||||
att: Math.max(1, m.att - (m.attDownTurns > 0 ? m.attDownDice || 0 : 0)),
|
||||
esq: m.esqHalfTurns > 0 ? Math.max(1, Math.floor(m.esq / 2)) : m.esq,
|
||||
vue: Math.max(1, m.vue - (m.vueMalusTurns > 0 ? m.vueMalus || 0 : 0)),
|
||||
armorPhys: m.armor || 0,
|
||||
armorMag: m.armorMag || 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1006,9 +1022,12 @@ function applyScrollZone(zone) {
|
||||
if (zone.type === "damage") {
|
||||
cdStart("💥 Effet de zone");
|
||||
for (const m of targets) {
|
||||
m.pv -= zone.total;
|
||||
cdLine(`${m.emoji} ${m.name} subit <span class="cd-val">${zone.total} points de dégâts</span> (${zone.label}).`);
|
||||
log(`💥 ${m.name} est pris dans l'explosion : −${zone.total} PV.`, "combat");
|
||||
// dégâts magiques : seule l'armure magique du monstre les réduit
|
||||
const armMag = Math.max(0, m.armorMag || 0);
|
||||
const dmg = Math.max(1, zone.total - armMag);
|
||||
m.pv -= dmg;
|
||||
cdLine(`${m.emoji} ${m.name} subit <span class="cd-val">${dmg} points de dégâts</span> (${zone.label}${armMag ? `, armure magique −${armMag}` : ""}).`);
|
||||
log(`💥 ${m.name} est pris dans l'explosion : −${dmg} PV.`, "combat");
|
||||
if (m.pv <= 0) killMonster(m, "scroll");
|
||||
}
|
||||
cdFlush();
|
||||
@@ -1077,7 +1096,7 @@ function passDLA() {
|
||||
cdLine(`Votre jet d'Esquive est de : <span class="cd-val">${r.esqRoll}</span> (${r.esqDice}D6)`);
|
||||
if (r.hit) {
|
||||
t.pv -= r.damage;
|
||||
cdLine(`Il vous a <span class="cd-bad">TOUCHÉ</span>${r.critical ? ` d'un <span class="cd-bad">coup critique</span>` : ""} et vous a infligé <span class="cd-bad">${r.damage} points de dégâts</span>.`);
|
||||
cdLine(`Il vous a <span class="cd-bad">TOUCHÉ</span>${r.critical ? ` d'un <span class="cd-bad">coup critique</span>` : ""} et vous a infligé <span class="cd-bad">${r.damage} points de dégâts</span>${r.armorReduction ? ` (votre armure a absorbé ${r.armorReduction} point(s))` : ""}.`);
|
||||
log(`${m.emoji} ${m.name} t'attaque : ${r.attRoll} vs ${r.esqRoll} → ${r.damage} dégâts !`, "bad");
|
||||
if (t.pv <= 0) { cdLine(`Il vous a <span class="cd-bad">TERRASSÉ</span>…`); cdFlush(); die(m); return; }
|
||||
} else {
|
||||
@@ -1254,11 +1273,12 @@ function renderPanels() {
|
||||
<div><span>Tour</span><span class="stat-val">${t.tour}</span></div>
|
||||
<div><span>PV</span><span class="stat-val">${t.pv} / ${t.pvMax}</span></div>
|
||||
<div class="hp-bar-wrap"><div class="hp-bar ${hpClass}" style="width:${pct * 100}%"></div></div>
|
||||
<div><span>Attaque</span><span class="stat-val">${fmtStatLine(t.att, te.att, 6, te.attFlat)}</span></div>
|
||||
<div><span>Esquive</span><span class="stat-val">${fmtStatLine(t.esq, te.esq, 6, te.esqFlat)}</span></div>
|
||||
<div><span>Dégâts</span><span class="stat-val">${fmtStatLine(t.deg, te.deg, 3, te.degFlat, te.degBonus)}</span></div>
|
||||
<div><span>Régénération</span><span class="stat-val">${fmtStatLine(t.reg, te.reg, 3, te.regFlat)}</span></div>
|
||||
<div><span>Armure</span><span class="stat-val">${te.armor}${t.armorDice ? "+" + t.armorDice + "D3" : ""}</span></div>
|
||||
<div><span>Attaque</span><span class="stat-val">${fmtStatLine(t.att, te.att, 6, te.attFlat, 0, { phys: te.attFlatPhys, mag: te.attFlatMag })}</span></div>
|
||||
<div><span>Esquive</span><span class="stat-val">${fmtStatLine(t.esq, te.esq, 6, te.esqFlat, 0, { phys: te.esqFlatPhys, mag: te.esqFlatMag })}</span></div>
|
||||
<div><span>Dégâts</span><span class="stat-val">${fmtStatLine(t.deg, te.deg, 3, te.degFlat, te.degBonus, { phys: te.degFlatPhys, mag: te.degFlatMag })}</span></div>
|
||||
<div><span>Régénération</span><span class="stat-val">${fmtStatLine(t.reg, te.reg, 3, te.regFlat, 0, { phys: te.regFlatPhys, mag: te.regFlatMag })}</span></div>
|
||||
<div><span>Armure phy.</span><span class="stat-val">${te.armorPhys}${t.armorDice ? "+" + t.armorDice + "D3" : ""}</span></div>
|
||||
<div><span>Armure mag.</span><span class="stat-val">${te.armorMag > 0 ? "+" : ""}${te.armorMag}</span></div>
|
||||
<div><span>Vue</span><span class="stat-val">${te.vue}</span></div>
|
||||
<div><span>${RACES[t.race].comp.name}</span><span class="stat-val">${t.comp.pct} %</span></div>
|
||||
<div><span>${RACES[t.race].sort.name}</span><span class="stat-val">${t.sort.pct} %</span></div>
|
||||
@@ -1283,7 +1303,7 @@ function renderPanels() {
|
||||
|
||||
const improve = $("improve");
|
||||
improve.innerHTML = "";
|
||||
const labels = { att: "Attaque +1D6", esq: "Esquive +1D6", deg: "Dégâts +1D3", reg: "Régén. +1D3", pv: "PV max +10", vue: "Vue +1", armor: "Armure +1D3" };
|
||||
const labels = { att: "Attaque +1D6", esq: "Esquive +1D6", deg: "Dégâts +1D3", reg: "Régén. +1D3", pv: "PV max +10", vue: "Vue +1", armor: "Armure phy. +1D3" };
|
||||
for (const stat of Object.keys(labels)) {
|
||||
const cost = improveCost(stat, t.bought[stat], t.race);
|
||||
const btn = document.createElement("button");
|
||||
|
||||
+3
-1
@@ -2,6 +2,8 @@
|
||||
* https://mountypedia.mountyhall.com/Mountyhall/{Arme,Armure,Casque,Bouclier,Talisman,Bottes}
|
||||
* Valeurs de base (sans templates) : TOUS les bonus sont fixes — ATT/ESQ/DEG/REG
|
||||
* s'ajoutent aux jets (jamais en dés), ARM/VUE/PV sont fixes, RM/MM en %.
|
||||
* Les bonus/malus d'équipement sont PHYSIQUES (dont l'armure, sauf exceptions
|
||||
* à venir) — les bonus magiques viennent des potions et parchemins.
|
||||
* `tier` (1-5) borne la profondeur de drop.
|
||||
* Les armes à deux mains (twoHanded) sont incompatibles avec un bouclier. */
|
||||
|
||||
@@ -92,7 +94,7 @@ const LEGACY_ARMOR_NAMES = ["Armure de cuir", "Haubert de mailles", "Armure de p
|
||||
|
||||
const GEAR_MOD_LABELS = [
|
||||
["att", "ATT", ""], ["esq", "ESQ", ""], ["deg", "DEG", ""],
|
||||
["reg", "REG", ""], ["arm", "Armure", ""], ["vue", "VUE", ""], ["pv", "PV", ""],
|
||||
["reg", "REG", ""], ["arm", "Armure phy.", ""], ["vue", "VUE", ""], ["pv", "PV", ""],
|
||||
["rmPct", "RM", " %"], ["mmPct", "MM", " %"],
|
||||
];
|
||||
|
||||
|
||||
+28
-11
@@ -400,37 +400,52 @@ function sumPotionMods(effects) {
|
||||
return m;
|
||||
}
|
||||
|
||||
/* Affichage caractéristique : valeur effective + rappel de la base si modifiée. */
|
||||
function fmtStatLine(base, eff, faces, flat = 0, extra = 0) {
|
||||
/* Affichage caractéristique : valeur effective + rappel de la base si modifiée.
|
||||
* `split` ({ phys, mag }) détaille l'origine du bonus fixe : P = physique
|
||||
* (équipement), M = magique (potions, parchemins). */
|
||||
function fmtStatLine(base, eff, faces, flat = 0, extra = 0, split = null) {
|
||||
const diceDelta = eff - base;
|
||||
const jetBonus = (flat || 0) + (extra || 0);
|
||||
let main = `${eff}D${faces}`;
|
||||
if (jetBonus) main += jetBonus > 0 ? ` +${jetBonus}` : ` ${jetBonus}`;
|
||||
const hints = [];
|
||||
if (split && split.phys && split.mag) {
|
||||
const f = v => `${v > 0 ? "+" : ""}${v}`;
|
||||
hints.push(`P ${f(split.phys)} · M ${f(split.mag)}`);
|
||||
}
|
||||
if (diceDelta !== 0) {
|
||||
const sign = diceDelta > 0 ? "+" : "";
|
||||
return `${main} <small class="stat-hint">(${base}D${faces} ${sign}${diceDelta})</small>`;
|
||||
hints.push(`${base}D${faces} ${sign}${diceDelta}`);
|
||||
}
|
||||
if (hints.length) return `${main} <small class="stat-hint">(${hints.join(" · ")})</small>`;
|
||||
return main;
|
||||
}
|
||||
|
||||
/* Caractéristiques effectives : base + effets magiques (potions, parchemins)
|
||||
* + équipement (troll.gearMods, recalculé par game.js à chaque équipement).
|
||||
* Les bonus ATT/ESQ/DEG/REG de l'équipement sont des bonus FIXES sur les jets,
|
||||
* jamais des dés supplémentaires. */
|
||||
* jamais des dés supplémentaires.
|
||||
* Bonus/malus PHYSIQUES (équipement) et MAGIQUES (potions, parchemins) sont
|
||||
* décomposés (xxxFlatPhys / xxxFlatMag) ; de même pour l'armure :
|
||||
* armorPhys = base + naturelle + équipement, armorMag = effets magiques.
|
||||
* Les dégâts physiques sont réduits par l'armure totale, les dégâts magiques
|
||||
* par la seule armure magique. */
|
||||
function effTroll(troll) {
|
||||
const m = sumPotionMods(troll.potionEffects);
|
||||
const g = troll.gearMods || {};
|
||||
const m = sumPotionMods(troll.potionEffects); // magique
|
||||
const g = troll.gearMods || {}; // physique
|
||||
return {
|
||||
att: Math.max(1, troll.att + m.att),
|
||||
attFlat: m.attFlat + (g.att || 0),
|
||||
attFlat: m.attFlat + (g.att || 0), attFlatPhys: g.att || 0, attFlatMag: m.attFlat,
|
||||
esq: Math.max(1, troll.esq + m.esq),
|
||||
esqFlat: m.esqFlat + (g.esq || 0),
|
||||
esqFlat: m.esqFlat + (g.esq || 0), esqFlatPhys: g.esq || 0, esqFlatMag: m.esqFlat,
|
||||
deg: Math.max(1, troll.deg + m.deg),
|
||||
degFlat: m.degFlat + (g.deg || 0),
|
||||
degFlat: m.degFlat + (g.deg || 0), degFlatPhys: g.deg || 0, degFlatMag: m.degFlat,
|
||||
reg: Math.max(1, troll.reg + m.reg),
|
||||
regFlat: m.regFlat + (g.reg || 0),
|
||||
regFlat: m.regFlat + (g.reg || 0), regFlatPhys: g.reg || 0, regFlatMag: m.regFlat,
|
||||
vue: Math.max(1, troll.vue + m.vue + (g.vue || 0)),
|
||||
armor: Math.max(0, troll.armor + m.armor + (g.arm || 0)),
|
||||
armorPhys: Math.max(0, troll.armor + (g.arm || 0)),
|
||||
armorMag: m.armor,
|
||||
armorDice: troll.armorDice,
|
||||
degBonus: troll.degBonus || 0,
|
||||
pvMax: troll.pvMax,
|
||||
@@ -504,8 +519,10 @@ function tickPotionTurns(troll, logFn) {
|
||||
return sumPotionMods(troll.potionEffects).dlaBonusPA;
|
||||
}
|
||||
|
||||
/* Les effets de potions/parchemins sont MAGIQUES : leur armure est de
|
||||
* l'armure magique (seule à réduire les dégâts magiques). */
|
||||
const EFFECT_MOD_LABELS = {
|
||||
att: "ATT", esq: "ESQ", deg: "DEG", reg: "REG", vue: "VUE", armor: "Armure",
|
||||
att: "ATT", esq: "ESQ", deg: "DEG", reg: "REG", vue: "VUE", armor: "Armure mag.",
|
||||
};
|
||||
|
||||
function formatEffectMods(effect) {
|
||||
|
||||
+6
-3
@@ -52,7 +52,8 @@ const TALENT_RULES = {
|
||||
effect: `La Rafale Psychique est la seule attaque <b>imparable</b> du jeu :<br>
|
||||
• Jet d'Attaque : <b>automatiquement réussi</b> (aucune esquive possible) ;<br>
|
||||
• Jet de Dégâts : <b>1 D3 par D3 de Dégâts</b> ;<br>
|
||||
• l'<b>armure physique est ignorée</b>.<br><br>
|
||||
• l'<b>armure physique est ignorée</b> (seule l'armure magique de la cible
|
||||
réduit les dégâts).<br><br>
|
||||
Un Jet de Résistance réussi de la part de la cible lui permet de ne subir
|
||||
que la moitié des dégâts.`,
|
||||
flavor: `Le Durakuir fronce les sourcils — un exploit en soi — et une onde de
|
||||
@@ -87,7 +88,8 @@ const TALENT_RULES = {
|
||||
L'attaque par Vampirisme a les caractéristiques suivantes :<br>
|
||||
• Jet d'Attaque : <b>2 D6 par 3 D3 de Dégâts</b> ;<br>
|
||||
• Jet de Dégâts : <b>1 D3 par D3 de Dégâts</b> ;<br>
|
||||
• l'<b>armure physique est ignorée</b> ;<br>
|
||||
• l'<b>armure physique est ignorée</b> (seule l'armure magique de la cible
|
||||
réduit les dégâts) ;<br>
|
||||
• Vampirisme : <b>50 % des dégâts infligés</b> rendus en PV.<br><br>
|
||||
Un Jet de Résistance réussi de la part de la cible lui permet de ne subir que
|
||||
la moitié des dégâts (et réduit d'autant le gain de PV). Si la cible est tuée,
|
||||
@@ -126,7 +128,8 @@ const TALENT_RULES = {
|
||||
• Jet d'Attaque : <b>1 D6 par case de Vue + 1 D6 par case de proximité</b>
|
||||
(bonus quand la cible est plus proche que la portée maximale) ;<br>
|
||||
• Jet de Dégâts : <b>1 D3 par 2 cases de Vue</b> ;<br>
|
||||
• l'<b>armure physique est ignorée</b>.<br><br>
|
||||
• l'<b>armure physique est ignorée</b> (seule l'armure magique de la cible
|
||||
réduit les dégâts).<br><br>
|
||||
Un Jet de Résistance réussi de la part de la cible lui permet de ne subir
|
||||
que la moitié des dégâts.`,
|
||||
flavor: `Une bille de lumière verte file entre les stalactites et explose en
|
||||
|
||||
+8
-5
@@ -12,17 +12,17 @@ const TREASURE_POTIONS = [
|
||||
["calvok", "2 à 6", "1 tour", ["Concentration −5·X %"]],
|
||||
["cervelle", "2 à 6", "1 tour", ["Concentration +5·X %"]],
|
||||
["chronometre", "1 à 5", "3 tours", ["TOUR accéléré : +X PA par tour"]],
|
||||
["corruption", "3 à 7", "5 tours", ["ATT +X D3", "ESQ −X D3", "DEG +X", "REG −X", "VUE −X", "Armure +X", "RM −Y %, MM −Z % (selon X)"]],
|
||||
["corruption", "3 à 7", "5 tours", ["ATT +X D3", "ESQ −X D3", "DEG +X", "REG −X", "VUE −X", "Armure mag. +X", "RM −Y %, MM −Z % (selon X)"]],
|
||||
["djhinTonik", "1 à 5", "4 tours", ["DEG +X", "REG +X", "PV +2D3 (immédiat)"]],
|
||||
["doverPowa", "11 à 100", "2 tours", ["MM +Y %", "RM −X %"]],
|
||||
["fertilite", "3 à 7", "5 tours", ["ATT +X D3", "DEG +X"]],
|
||||
["feu", "3 à 7", "5 tours", ["ESQ +X D3", "VUE +X"]],
|
||||
["glacier", "3 à 7", "5 tours", ["REG +X", "Armure +X"]],
|
||||
["glacier", "3 à 7", "5 tours", ["REG +X", "Armure mag. +X"]],
|
||||
["grippe", "3 à 4", "3 tours", ["ATT −X D3", "ESQ −X D3", "DEG −X", "REG −X"]],
|
||||
["guerison", "1 à 5", "immédiat", ["PV +2·X D3"]],
|
||||
["kouleMann", "1 à 5", "4 tours", ["REG +X", "VUE +X", "PV +(X/2) D3 (immédiat)"]],
|
||||
["longueVue", "1, 2, 3, 5 ou 8", "3 tours", ["VUE +X"]],
|
||||
["metomol", "1 à 5", "2 tours", ["TOUR accéléré : +X PA par tour", "Armure −2·X"]],
|
||||
["metomol", "1 à 5", "2 tours", ["TOUR accéléré : +X PA par tour", "Armure mag. −2·X"]],
|
||||
["painture", "1 à 5", "X tours", ["Visible — camouflage impossible"]],
|
||||
["pneumonie", "5 (fixe)", "3 tours", ["ATT −5 D3", "ESQ −5 D3", "DEG −5", "REG −5"]],
|
||||
["pufPuff", "0 à 2", "3 tours", ["ATT −X D3", "ESQ −X D3", "VUE −(X+1)", "PV −2D3 si X ≥ 2", "Effet de Zone"]],
|
||||
@@ -66,6 +66,8 @@ function treasuresRender() {
|
||||
<a href="https://mountypedia.mountyhall.com/Mountyhall/Potion" target="_blank" rel="noopener">Mountypedia</a>.
|
||||
Un trésor ne fait qu'<b>un seul jet de dés</b> : une formule « X D3 » donne le même total
|
||||
partout où elle s'applique. Les bonus « +X » sont des valeurs fixes ajoutées aux jets.
|
||||
Tous les bonus/malus des potions et parchemins sont <b>magiques</b> — leur armure est de
|
||||
l'<b>armure magique</b>, seule à réduire les dégâts magiques.
|
||||
1 tour = 1 DLA. Boire une potion ou lire un parchemin coûte <b>1 PA</b>.</p>`;
|
||||
|
||||
html += `<h2 class="tz-section">🧪 Potions (${TREASURE_POTIONS.length})</h2><div class="tz-grid">`;
|
||||
@@ -83,8 +85,9 @@ function treasuresRender() {
|
||||
const gearCount = Object.values(GEAR).reduce((n, list) => n + list.length, 0);
|
||||
html += `<h2 class="tz-section">⚔️ Équipement (${gearCount})</h2>
|
||||
<p class="tz-intro">Six emplacements : ${Object.values(GEAR_SLOTS).map(s => s.label).join(", ")}.
|
||||
Tous les bonus d'équipement sont <b>fixes</b> : ATT/ESQ/DEG/REG s'ajoutent aux jets
|
||||
(jamais en dés), Armure/VUE/PV aussi, RM/MM en %.
|
||||
Tous les bonus d'équipement sont <b>fixes</b> et <b>physiques</b> : ATT/ESQ/DEG/REG
|
||||
s'ajoutent aux jets (jamais en dés), Armure physique/VUE/PV aussi, RM/MM en %.
|
||||
L'armure d'équipement réduit les dégâts physiques mais pas les dégâts magiques.
|
||||
Une arme <b>à 2 mains</b> est incompatible avec un bouclier. S'équiper coûte <b>2 PA</b>.
|
||||
Valeurs de base de la Mountypedia (sans templates) ; les objets puissants ne se trouvent
|
||||
qu'en profondeur.</p>`;
|
||||
|
||||
Reference in New Issue
Block a user