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:
@@ -39,6 +39,8 @@ lance directement une partie (utilisé pour les captures d'écran et les tests).
|
|||||||
| Combat | Somme ATT D6 vs somme ESQ D6, dégâts DEG D3 − armure, coup critique (attaque ≥ 2 × esquive) aux dégâts doublés | Identique, avec un panneau « Détail du combat » au format des rapports MH (jets, Seuil de Résistance, jet de Résistance, maîtrise, butin, PX) à droite du journal |
|
| Combat | Somme ATT D6 vs somme ESQ D6, dégâts DEG D3 − armure, coup critique (attaque ≥ 2 × esquive) aux dégâts doublés | Identique, avec un panneau « Détail du combat » au format des rapports MH (jets, Seuil de Résistance, jet de Résistance, maîtrise, butin, PX) à droite du journal |
|
||||||
| Talents | Compétence + sortilège réservés par race, 15 % de maîtrise initiale, jet D100 sous le %, progression à l'usage (+1D6 % < 50, +1D3 % < 75, +1 % ensuite ; sous 50 %, un échec donne quand même +1 %), plafonds 90 % compétences / 80 % sortilèges, échec = PA partiellement remboursés | Identique |
|
| Talents | Compétence + sortilège réservés par race, 15 % de maîtrise initiale, jet D100 sous le %, progression à l'usage (+1D6 % < 50, +1D3 % < 75, +1 % ensuite ; sous 50 %, un échec donne quand même +1 %), plafonds 90 % compétences / 80 % sortilèges, échec = PA partiellement remboursés | Identique |
|
||||||
| Magie | Seuil de Résistance basé sur MM vs RM, borné 10–90 % | Identique : jet de résistance des monstres → effets de moitié |
|
| Magie | Seuil de Résistance basé sur MM vs RM, borné 10–90 % | Identique : jet de résistance des monstres → effets de moitié |
|
||||||
|
| Bonus/malus | Physiques (équipement) et magiques (potions, sortilèges…) distincts | Identique : équipement = physique (exceptions à venir), potions/parchemins = magique ; décomposition « P/M » affichée dans le profil |
|
||||||
|
| Armure | L'armure réduit les dégâts physiques ; seule sa composante magique réduit les dégâts magiques | Identique : armure physique (naturelle en D3 + équipement) + armure magique (potions/parchemins) — troll et monstres |
|
||||||
| Progression | PX → PI, le N-ième achat coûte N × coût de base racial (table de Rules_3.php) | Identique : 16 PI de base (12 pour la caractéristique favorite), REG 30 (Darkling 22), Armure naturelle 30 pour tous, PV par tranche de 10 |
|
| Progression | PX → PI, le N-ième achat coûte N × coût de base racial (table de Rules_3.php) | Identique : 16 PI de base (12 pour la caractéristique favorite), REG 30 (Darkling 22), Armure naturelle 30 pour tous, PV par tranche de 10 |
|
||||||
| Niveaux | 10 × N PI pour atteindre le niveau N | Identique |
|
| Niveaux | 10 × N PI pour atteindre le niveau N | Identique |
|
||||||
| Races | 5 races, chacune avec sort/compétence propre | Les 5 races avec leur capacité signature (voir ci-dessous) |
|
| Races | 5 races, chacune avec sort/compétence propre | Les 5 races avec leur capacité signature (voir ci-dessous) |
|
||||||
@@ -152,6 +154,17 @@ non affilié au jeu original de Mountyhall SARL.
|
|||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
|
- **1.9.0** (2026-06-12) — Bonus/malus **physiques** et **magiques** distincts, comme
|
||||||
|
à MountyHall : l'équipement donne des bonus physiques (exceptions à venir), les
|
||||||
|
potions et parchemins des bonus magiques. L'armure est scindée en **armure
|
||||||
|
physique** (base + naturelle en D3 + équipement) et **armure magique** (effets
|
||||||
|
de potions/parchemins, ex. Extrait du Glacier) : les dégâts physiques sont
|
||||||
|
réduits par l'armure totale, les dégâts magiques (Rafale Psychique, Vampirisme,
|
||||||
|
Projectile Magique, Rune Explosive) par la seule armure magique — le Siphon des
|
||||||
|
Âmes continue d'ignorer toute armure. Les monstres ont eux aussi une armure
|
||||||
|
physique et une armure magique distinctes (valeurs du bestiaire à venir).
|
||||||
|
Affichage : Armure phy./mag. séparées dans le profil, décomposition « P/M » des
|
||||||
|
bonus fixes, absorption d'armure détaillée dans les rapports de combat.
|
||||||
- **1.8.0** (2026-06-12) — Habillage « pack parchemin » officiel de MountyHall
|
- **1.8.0** (2026-06-12) — Habillage « pack parchemin » officiel de MountyHall
|
||||||
(https://www.mountyhall.com/MH_Pack/packMH_parchemin/) : fond lin, tableaux
|
(https://www.mountyhall.com/MH_Pack/packMH_parchemin/) : fond lin, tableaux
|
||||||
crème à bordure noire (tableau1/2.jpg), liens rouge sombre #990000, boutons
|
crème à bordure noire (tableau1/2.jpg), liens rouge sombre #990000, boutons
|
||||||
|
|||||||
+56
-36
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
"use strict";
|
"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. */
|
/* Alpha : maîtrise initiale haute pour les tests. Remettre 15 % / 15 % à la v1.0 officielle. */
|
||||||
const START_COMP_PCT = 90;
|
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.
|
/* 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.
|
* 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 = {}) {
|
function resolveAttack(attacker, defender, opts = {}) {
|
||||||
const att = rollDice(attacker.att, 6);
|
const att = rollDice(attacker.att, 6);
|
||||||
const esq = rollDice(defender.esq, 6);
|
const esq = rollDice(defender.esq, 6);
|
||||||
@@ -50,9 +53,16 @@ function resolveAttack(attacker, defender, opts = {}) {
|
|||||||
const deg = rollDice(attacker.deg, 3);
|
const deg = rollDice(attacker.deg, 3);
|
||||||
const degFlat = attacker.degFlat || 0;
|
const degFlat = attacker.degFlat || 0;
|
||||||
result.rawDamage = (deg.total + (attacker.degBonus || 0) + degFlat) * (result.critical ? 2 : 1);
|
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)
|
// armure physique : fixe (base + équipement) + naturelle en D3 (achetée en PI)
|
||||||
const armor = opts.ignoreArmor ? 0
|
// armure magique : effets de potions/parchemins (armorMag, peut être négative)
|
||||||
: (defender.armor || 0) + (defender.armorDice ? rollDice(defender.armorDice, 3).total : 0);
|
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);
|
result.damage = Math.max(1, result.rawDamage - armor);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -126,21 +136,21 @@ const RACES = {
|
|||||||
desc: "Les tanks du Hall, durs au mal et infatigables.",
|
desc: "Les tanks du Hall, durs au mal et infatigables.",
|
||||||
stats: { att: 3, esq: 3, deg: 3, reg: 1, pvMax: 40, vue: 3 },
|
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." },
|
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: {
|
Kastar: {
|
||||||
emoji: "🔴", favored: "deg",
|
emoji: "🔴", favored: "deg",
|
||||||
desc: "Les vampires du Hall : leurs coups les nourrissent.",
|
desc: "Les vampires du Hall : leurs coups les nourrissent.",
|
||||||
stats: { att: 3, esq: 3, deg: 4, reg: 1, pvMax: 30, vue: 3 },
|
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." },
|
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: {
|
Tomawak: {
|
||||||
emoji: "🟡", favored: "vue",
|
emoji: "🟡", favored: "vue",
|
||||||
desc: "Trõlls furtifs, chasseurs embusqués aux yeux perçants.",
|
desc: "Trõlls furtifs, chasseurs embusqués aux yeux perçants.",
|
||||||
stats: { att: 3, esq: 3, deg: 3, reg: 1, pvMax: 30, vue: 4 },
|
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é." },
|
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: {
|
Darkling: {
|
||||||
emoji: "🟣", favored: "reg",
|
emoji: "🟣", favored: "reg",
|
||||||
@@ -153,18 +163,21 @@ const RACES = {
|
|||||||
|
|
||||||
/* ================= Bestiaire ================= */
|
/* ================= 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 = [
|
const MONSTER_TYPES = [
|
||||||
{ name: "Gobelin", emoji: "👺", level: 1, att: 2, esq: 2, deg: 2, pv: 12, armor: 0, vue: 4 },
|
{ 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, vue: 1, static: true },
|
{ 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, vue: 5 },
|
{ 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, vue: 4 },
|
{ 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, vue: 3 },
|
{ 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, vue: 6 },
|
{ 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, vue: 3 },
|
{ 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. */
|
/* Gabarits d'âge façon MountyHall : plus on descend, plus les bêtes sont vieilles. */
|
||||||
const TEMPLATES = [
|
const TEMPLATES = [
|
||||||
@@ -183,7 +196,7 @@ function applyTemplate(type, tpl, x, y) {
|
|||||||
esq: Math.max(1, Math.round(type.esq * tpl.mult)),
|
esq: Math.max(1, Math.round(type.esq * tpl.mult)),
|
||||||
deg: Math.max(1, Math.round(type.deg * 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),
|
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,
|
x, y, boss: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -608,7 +621,7 @@ function attackMonster(m, opts = {}) {
|
|||||||
if (r.hit) {
|
if (r.hit) {
|
||||||
m.pv -= r.damage;
|
m.pv -= r.damage;
|
||||||
gainPX(1); // +1 PX par attaque réussie (converti en PI)
|
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");
|
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");
|
if (m.pv <= 0) killMonster(m, "attack");
|
||||||
} else {
|
} else {
|
||||||
@@ -813,12 +826,13 @@ function useSort() {
|
|||||||
if (!tryTalent(t.sort, 80, sort.cost, "🔮 Rafale Psychique", "sort")) { cdFlush(); afterAction(); return; }
|
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(`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).`);
|
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);
|
const res = resistInfo(m);
|
||||||
if (res) dmg = Math.max(1, Math.ceil(dmg / 2));
|
if (res) dmg = Math.max(1, Math.ceil(dmg / 2));
|
||||||
m.pv -= dmg;
|
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${armMag ? `, armure magique −${armMag}` : ""}).`);
|
||||||
log(`Rafale Psychique : touche automatique, ${dmg} dégâts${res ? " (résisté : moitié)" : ""}, armure ignorée.`, "combat");
|
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");
|
if (m.pv <= 0) killMonster(m, "sort");
|
||||||
} else if (t.race === "Kastar") { // Vampirisme : dégâts + vol de vie
|
} else if (t.race === "Kastar") { // Vampirisme : dégâts + vol de vie
|
||||||
const m = adjacentMonster();
|
const m = adjacentMonster();
|
||||||
@@ -829,7 +843,7 @@ function useSort() {
|
|||||||
att: Math.max(1, Math.floor(te.deg * 2 / 3)), deg: te.deg,
|
att: Math.max(1, Math.floor(te.deg * 2 / 3)), deg: te.deg,
|
||||||
degBonus: te.degBonus, attFlat: te.attFlat, degFlat: te.degFlat,
|
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);
|
cdAttackRolls(r);
|
||||||
if (r.hit) {
|
if (r.hit) {
|
||||||
let dmg = r.damage;
|
let dmg = r.damage;
|
||||||
@@ -838,7 +852,7 @@ function useSort() {
|
|||||||
m.pv -= dmg;
|
m.pv -= dmg;
|
||||||
const heal = Math.ceil(dmg / 2);
|
const heal = Math.ceil(dmg / 2);
|
||||||
t.pv = Math.min(t.pvMax, t.pv + heal);
|
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.`);
|
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");
|
log(`Vampirisme : ${dmg} dégâts${res ? " (résisté)" : ""}, tu draines ${heal} PV.`, "good");
|
||||||
if (m.pv <= 0) killMonster(m, "sort");
|
if (m.pv <= 0) killMonster(m, "sort");
|
||||||
@@ -853,14 +867,14 @@ function useSort() {
|
|||||||
const proxBonus = Math.max(0, te.vue - dist);
|
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).`);
|
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 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);
|
cdAttackRolls(r);
|
||||||
if (r.hit) {
|
if (r.hit) {
|
||||||
let dmg = r.damage;
|
let dmg = r.damage;
|
||||||
const res = resistInfo(m);
|
const res = resistInfo(m);
|
||||||
if (res) dmg = Math.max(1, Math.ceil(dmg / 2));
|
if (res) dmg = Math.max(1, Math.ceil(dmg / 2));
|
||||||
m.pv -= dmg;
|
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");
|
log(`Projectile Magique sur ${m.name} (distance ${dist}) : ${dmg} dégâts${res ? " (résisté)" : ""}.`, "combat");
|
||||||
if (m.pv <= 0) killMonster(m, "sort");
|
if (m.pv <= 0) killMonster(m, "sort");
|
||||||
} else {
|
} else {
|
||||||
@@ -907,6 +921,8 @@ function effMonster(m) {
|
|||||||
att: Math.max(1, m.att - (m.attDownTurns > 0 ? m.attDownDice || 0 : 0)),
|
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,
|
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)),
|
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") {
|
if (zone.type === "damage") {
|
||||||
cdStart("💥 Effet de zone");
|
cdStart("💥 Effet de zone");
|
||||||
for (const m of targets) {
|
for (const m of targets) {
|
||||||
m.pv -= zone.total;
|
// dégâts magiques : seule l'armure magique du monstre les réduit
|
||||||
cdLine(`${m.emoji} ${m.name} subit <span class="cd-val">${zone.total} points de dégâts</span> (${zone.label}).`);
|
const armMag = Math.max(0, m.armorMag || 0);
|
||||||
log(`💥 ${m.name} est pris dans l'explosion : −${zone.total} PV.`, "combat");
|
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");
|
if (m.pv <= 0) killMonster(m, "scroll");
|
||||||
}
|
}
|
||||||
cdFlush();
|
cdFlush();
|
||||||
@@ -1077,7 +1096,7 @@ function passDLA() {
|
|||||||
cdLine(`Votre jet d'Esquive est de : <span class="cd-val">${r.esqRoll}</span> (${r.esqDice}D6)`);
|
cdLine(`Votre jet d'Esquive est de : <span class="cd-val">${r.esqRoll}</span> (${r.esqDice}D6)`);
|
||||||
if (r.hit) {
|
if (r.hit) {
|
||||||
t.pv -= r.damage;
|
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");
|
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; }
|
if (t.pv <= 0) { cdLine(`Il vous a <span class="cd-bad">TERRASSÉ</span>…`); cdFlush(); die(m); return; }
|
||||||
} else {
|
} else {
|
||||||
@@ -1254,11 +1273,12 @@ function renderPanels() {
|
|||||||
<div><span>Tour</span><span class="stat-val">${t.tour}</span></div>
|
<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><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 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>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)}</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)}</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)}</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</span><span class="stat-val">${te.armor}${t.armorDice ? "+" + t.armorDice + "D3" : ""}</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>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].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>
|
<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");
|
const improve = $("improve");
|
||||||
improve.innerHTML = "";
|
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)) {
|
for (const stat of Object.keys(labels)) {
|
||||||
const cost = improveCost(stat, t.bought[stat], t.race);
|
const cost = improveCost(stat, t.bought[stat], t.race);
|
||||||
const btn = document.createElement("button");
|
const btn = document.createElement("button");
|
||||||
|
|||||||
+3
-1
@@ -2,6 +2,8 @@
|
|||||||
* https://mountypedia.mountyhall.com/Mountyhall/{Arme,Armure,Casque,Bouclier,Talisman,Bottes}
|
* 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
|
* 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 %.
|
* 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.
|
* `tier` (1-5) borne la profondeur de drop.
|
||||||
* Les armes à deux mains (twoHanded) sont incompatibles avec un bouclier. */
|
* 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 = [
|
const GEAR_MOD_LABELS = [
|
||||||
["att", "ATT", ""], ["esq", "ESQ", ""], ["deg", "DEG", ""],
|
["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", " %"],
|
["rmPct", "RM", " %"], ["mmPct", "MM", " %"],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+28
-11
@@ -400,37 +400,52 @@ function sumPotionMods(effects) {
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Affichage caractéristique : valeur effective + rappel de la base si modifiée. */
|
/* Affichage caractéristique : valeur effective + rappel de la base si modifiée.
|
||||||
function fmtStatLine(base, eff, faces, flat = 0, extra = 0) {
|
* `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 diceDelta = eff - base;
|
||||||
const jetBonus = (flat || 0) + (extra || 0);
|
const jetBonus = (flat || 0) + (extra || 0);
|
||||||
let main = `${eff}D${faces}`;
|
let main = `${eff}D${faces}`;
|
||||||
if (jetBonus) main += jetBonus > 0 ? ` +${jetBonus}` : ` ${jetBonus}`;
|
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) {
|
if (diceDelta !== 0) {
|
||||||
const sign = 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;
|
return main;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Caractéristiques effectives : base + effets magiques (potions, parchemins)
|
/* Caractéristiques effectives : base + effets magiques (potions, parchemins)
|
||||||
* + équipement (troll.gearMods, recalculé par game.js à chaque équipement).
|
* + é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,
|
* 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) {
|
function effTroll(troll) {
|
||||||
const m = sumPotionMods(troll.potionEffects);
|
const m = sumPotionMods(troll.potionEffects); // magique
|
||||||
const g = troll.gearMods || {};
|
const g = troll.gearMods || {}; // physique
|
||||||
return {
|
return {
|
||||||
att: Math.max(1, troll.att + m.att),
|
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),
|
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),
|
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),
|
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)),
|
vue: Math.max(1, troll.vue + m.vue + (g.vue || 0)),
|
||||||
armor: Math.max(0, troll.armor + m.armor + (g.arm || 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,
|
armorDice: troll.armorDice,
|
||||||
degBonus: troll.degBonus || 0,
|
degBonus: troll.degBonus || 0,
|
||||||
pvMax: troll.pvMax,
|
pvMax: troll.pvMax,
|
||||||
@@ -504,8 +519,10 @@ function tickPotionTurns(troll, logFn) {
|
|||||||
return sumPotionMods(troll.potionEffects).dlaBonusPA;
|
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 = {
|
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) {
|
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>
|
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 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>
|
• 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
|
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.`,
|
que la moitié des dégâts.`,
|
||||||
flavor: `Le Durakuir fronce les sourcils — un exploit en soi — et une onde de
|
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>
|
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 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>
|
• 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>
|
• 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
|
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,
|
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>
|
• 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>
|
(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>
|
• 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
|
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.`,
|
que la moitié des dégâts.`,
|
||||||
flavor: `Une bille de lumière verte file entre les stalactites et explose en
|
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 %"]],
|
["calvok", "2 à 6", "1 tour", ["Concentration −5·X %"]],
|
||||||
["cervelle", "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"]],
|
["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)"]],
|
["djhinTonik", "1 à 5", "4 tours", ["DEG +X", "REG +X", "PV +2D3 (immédiat)"]],
|
||||||
["doverPowa", "11 à 100", "2 tours", ["MM +Y %", "RM −X %"]],
|
["doverPowa", "11 à 100", "2 tours", ["MM +Y %", "RM −X %"]],
|
||||||
["fertilite", "3 à 7", "5 tours", ["ATT +X D3", "DEG +X"]],
|
["fertilite", "3 à 7", "5 tours", ["ATT +X D3", "DEG +X"]],
|
||||||
["feu", "3 à 7", "5 tours", ["ESQ +X D3", "VUE +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"]],
|
["grippe", "3 à 4", "3 tours", ["ATT −X D3", "ESQ −X D3", "DEG −X", "REG −X"]],
|
||||||
["guerison", "1 à 5", "immédiat", ["PV +2·X D3"]],
|
["guerison", "1 à 5", "immédiat", ["PV +2·X D3"]],
|
||||||
["kouleMann", "1 à 5", "4 tours", ["REG +X", "VUE +X", "PV +(X/2) D3 (immédiat)"]],
|
["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"]],
|
["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"]],
|
["painture", "1 à 5", "X tours", ["Visible — camouflage impossible"]],
|
||||||
["pneumonie", "5 (fixe)", "3 tours", ["ATT −5 D3", "ESQ −5 D3", "DEG −5", "REG −5"]],
|
["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"]],
|
["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>.
|
<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
|
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.
|
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>`;
|
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">`;
|
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);
|
const gearCount = Object.values(GEAR).reduce((n, list) => n + list.length, 0);
|
||||||
html += `<h2 class="tz-section">⚔️ Équipement (${gearCount})</h2>
|
html += `<h2 class="tz-section">⚔️ Équipement (${gearCount})</h2>
|
||||||
<p class="tz-intro">Six emplacements : ${Object.values(GEAR_SLOTS).map(s => s.label).join(", ")}.
|
<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
|
Tous les bonus d'équipement sont <b>fixes</b> et <b>physiques</b> : ATT/ESQ/DEG/REG
|
||||||
(jamais en dés), Armure/VUE/PV aussi, RM/MM en %.
|
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>.
|
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
|
Valeurs de base de la Mountypedia (sans templates) ; les objets puissants ne se trouvent
|
||||||
qu'en profondeur.</p>`;
|
qu'en profondeur.</p>`;
|
||||||
|
|||||||
@@ -76,6 +76,61 @@ assert.strictEqual(g.improveCost("reg", 0, "Skrim"), 30, "REG non favorisée = 3
|
|||||||
assert.strictEqual(g.improveCost("armor", 0, "Durakuir"), 30, "Armure = 30 PI pour tous");
|
assert.strictEqual(g.improveCost("armor", 0, "Durakuir"), 30, "Armure = 30 PI pour tous");
|
||||||
assert.strictEqual(g.improveCost("esq", 1, "Tomawak"), 32, "2e dé d'ESQ = 32 PI");
|
assert.strictEqual(g.improveCost("esq", 1, "Tomawak"), 32, "2e dé d'ESQ = 32 PI");
|
||||||
|
|
||||||
|
// Armure physique vs magique : les dégâts physiques sont réduits par l'armure
|
||||||
|
// totale ; les dégâts magiques (opts.magic) par la seule armure magique ;
|
||||||
|
// opts.ignoreArmor ignore tout (Siphon des Âmes).
|
||||||
|
{
|
||||||
|
const fort = { att: 50, deg: 10 }; // touche toujours, dégâts élevés
|
||||||
|
for (let i = 0; i < 200; i++) {
|
||||||
|
const cible = { esq: 1, armorPhys: 5, armorMag: 3 };
|
||||||
|
const phy = g.resolveAttack(fort, cible);
|
||||||
|
assert.strictEqual(phy.armorReduction, 8, "attaque physique : armure totale 5+3");
|
||||||
|
const mag = g.resolveAttack(fort, cible, { magic: true });
|
||||||
|
assert.strictEqual(mag.armorReduction, 3, "attaque magique : armure magique seule");
|
||||||
|
const tout = g.resolveAttack(fort, cible, { ignoreArmor: true });
|
||||||
|
assert.strictEqual(tout.armorReduction, 0, "ignoreArmor : aucune réduction");
|
||||||
|
}
|
||||||
|
// armure magique négative (Métomol) : réduit l'armure totale, jamais sous 0
|
||||||
|
const r1 = g.resolveAttack(fort, { esq: 1, armorPhys: 2, armorMag: -5 });
|
||||||
|
assert.strictEqual(r1.armorReduction, 0, "armure totale plancher 0");
|
||||||
|
const r2 = g.resolveAttack(fort, { esq: 1, armorPhys: 0, armorMag: -4 }, { magic: true });
|
||||||
|
assert.strictEqual(r2.armorReduction, 0, "armure magique plancher 0");
|
||||||
|
// rétrocompat : un défenseur avec seulement `armor` = armure physique
|
||||||
|
const r3 = g.resolveAttack(fort, { esq: 1, armor: 4 });
|
||||||
|
assert.strictEqual(r3.armorReduction, 4, "fallback armor = physique");
|
||||||
|
}
|
||||||
|
|
||||||
|
// effTroll : équipement → armure physique, potions → armure magique
|
||||||
|
{
|
||||||
|
const gearLib = require("../js/gear.js");
|
||||||
|
const t = { att: 3, esq: 3, deg: 3, reg: 1, vue: 3, armor: 0, armorDice: 0, degBonus: 0, pvMax: 30, pv: 20,
|
||||||
|
potionEffects: [], blockCamoTurns: 0, tour: 1, bag: [],
|
||||||
|
equip: { arme: null, armure: null, casque: null, bouclier: null, talisman: null, bottes: null }, gearMods: null };
|
||||||
|
g.equipGear(t, gearLib.gearItemByName("armure", "Armure de cuir")); // arm +4 (physique)
|
||||||
|
p.drinkPotion(t, p.makePotionItem("glacier", 5), g.rollDice, () => {}); // Armure mag. +5
|
||||||
|
const e = p.effTroll(t);
|
||||||
|
assert.strictEqual(e.armorPhys, 4, "Armure de cuir : armure physique +4");
|
||||||
|
assert.strictEqual(e.armorMag, 5, "Extrait du Glacier : armure magique +5");
|
||||||
|
assert.strictEqual(e.armor, 9, "armure totale = physique + magique");
|
||||||
|
// décomposition des bonus fixes : équipement = physique, potion = magique
|
||||||
|
g.equipGear(t, gearLib.gearItemByName("arme", "Gourdin")); // att +2 (physique)
|
||||||
|
p.drinkPotion(t, p.makePotionItem("bonneBouffe", 5), g.rollDice, () => {}); // DEG +5 (magique)
|
||||||
|
const e2 = p.effTroll(t);
|
||||||
|
assert.strictEqual(e2.attFlatPhys, 2, "Gourdin : ATT +2 physique");
|
||||||
|
assert.strictEqual(e2.attFlatMag, 0, "aucun bonus magique d'ATT");
|
||||||
|
assert.strictEqual(e2.degFlatPhys, 5, "Gourdin : DEG +5 physique");
|
||||||
|
assert.strictEqual(e2.degFlatMag, 5, "Bonne Bouffe : DEG +5 magique");
|
||||||
|
assert.strictEqual(e2.degFlat, 10, "total = physique + magique");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monstres : armure physique et magique distinctes
|
||||||
|
{
|
||||||
|
const m = g.makeMonster(3, 1, 1);
|
||||||
|
assert(typeof m.armorMag === "number", "monstre : armorMag présent");
|
||||||
|
const boss = g.monsterFromSpec({ x: 1, y: 1, boss: true });
|
||||||
|
assert(typeof boss.armorMag === "number", "Béhémoth : armorMag présent");
|
||||||
|
}
|
||||||
|
|
||||||
// L'armure naturelle en D3 réduit bien les dégâts
|
// L'armure naturelle en D3 réduit bien les dégâts
|
||||||
{
|
{
|
||||||
let withArmor = 0, without = 0;
|
let withArmor = 0, without = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user