0.1.5 : modification calcul PX

This commit is contained in:
Cedric
2026-06-11 16:53:26 +02:00
parent 92df54fca2
commit fa3ff29709
2 changed files with 65 additions and 23 deletions
+58 -22
View File
@@ -80,6 +80,11 @@ function levelFromTotalPI(totalPI) {
return level; return level;
} }
/* PX de mise à mort : 10 + 2 × (niveau cible niveau troll) + niveau cible. */
function killPX(trollLevel, targetLevel) {
return 10 + 2 * (targetLevel - trollLevel) + targetLevel;
}
/* ================= Les 5 races ================= */ /* ================= Les 5 races ================= */
/* Profils de base officiels (mountyhall.com/MH_Rules/Races_*.php) : /* Profils de base officiels (mountyhall.com/MH_Rules/Races_*.php) :
@@ -305,6 +310,7 @@ function newGame(name, race, customLevel = null) {
degBonus: 0, armor: 0, armorDice: 0, degBonus: 0, armor: 0, armorDice: 0,
bought: { att: 0, esq: 0, deg: 0, reg: 0, pv: 0, vue: 0, armor: 0 }, bought: { att: 0, esq: 0, deg: 0, reg: 0, pv: 0, vue: 0, armor: 0 },
comp: { pct: 15 }, sort: { pct: 15 }, fatigue: 0, compUsed: false, comp: { pct: 15 }, sort: { pct: 15 }, fatigue: 0, compUsed: false,
compPXTurn: false, sortPXTurn: false,
weapon: null, armorItem: null, weapon: null, armorItem: null,
pa: PA_PER_TURN, pi: 0, totalPI: 0, gold: 0, pa: PA_PER_TURN, pi: 0, totalPI: 0, gold: 0,
bag: [], camo: false, kills: 0, dla: 1, bag: [], camo: false, kills: 0, dla: 1,
@@ -465,6 +471,14 @@ function cdFlush() {
/* Gain de PX (convertis en PI) comptabilisé dans l'action en cours. */ /* Gain de PX (convertis en PI) comptabilisé dans l'action en cours. */
function gainPX(n) { function gainPX(n) {
if (!n) return;
G.troll.pi += n;
G.troll.totalPI += n;
if (G.actionPX !== undefined) G.actionPX += n;
}
function adjustPX(n) {
if (!n) return;
G.troll.pi += n; G.troll.pi += n;
G.troll.totalPI += n; G.troll.totalPI += n;
if (G.actionPX !== undefined) G.actionPX += n; if (G.actionPX !== undefined) G.actionPX += n;
@@ -545,7 +559,7 @@ function attackMonster(m, opts = {}) {
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>.`);
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); if (m.pv <= 0) killMonster(m, "attack");
} else { } else {
log(`Tu attaques ${m.name} : ATT ${r.attRoll} vs ESQ ${r.esqRoll} → esquivé !`, "combat"); log(`Tu attaques ${m.name} : ATT ${r.attRoll} vs ESQ ${r.esqRoll} → esquivé !`, "combat");
} }
@@ -559,12 +573,28 @@ function attackMonster(m, opts = {}) {
return true; return true;
} }
function killMonster(m) { function killMonster(m, source = "attack") {
const px = m.level * 2; const trollLvl = levelFromTotalPI(G.troll.totalPI);
gainPX(px); const rawKillPx = killPX(trollLvl, m.level);
let killGain = 0;
if (rawKillPx < 0) {
if (source === "attack") adjustPX(-1); // annule le +1 PX d'attaque réussie
// comp/sort : on conserve uniquement le PX de talent déjà gagné
} else {
killGain = rawKillPx;
gainPX(killGain);
}
G.troll.kills++; G.troll.kills++;
cdLine(`Vous l'avez <span class="cd-good">TUÉ</span> et vous avez débarrassé le Monde Souterrain de sa présence maléfique.`); cdLine(`Vous l'avez <span class="cd-good">TUÉ</span> et vous avez débarrassé le Monde Souterrain de sa présence maléfique.`);
log(`💀 ${m.name} est terrassé ! +${px} PX (convertis en PI à l'entraînement).`, "good"); if (killGain > 0) {
log(`💀 ${m.name} est terrassé ! +${killGain} PX de mise à mort (convertis en PI à l'entraînement).`, "good");
} else if (rawKillPx < 0 && source !== "attack") {
log(`💀 ${m.name} est terrassé ! Pas de bonus de mise à mort (adversaire trop faible).`, "good");
} else if (rawKillPx < 0) {
log(`💀 ${m.name} est terrassé ! Pas de PX (adversaire trop faible).`, "good");
} else {
log(`💀 ${m.name} est terrassé !`, "good");
}
// butin : les monstres lâchent parfois un trésor en mourant // butin : les monstres lâchent parfois un trésor en mourant
if (Math.random() < 0.4 && !G.items.some(i => i.x === m.x && i.y === m.y)) { if (Math.random() < 0.4 && !G.items.some(i => i.x === m.x && i.y === m.y)) {
const drop = Math.random() < 0.5 const drop = Math.random() < 0.5
@@ -598,7 +628,7 @@ function cdClose() {
/* Tente le talent : dépense les PA (moitié remboursée en cas d'échec, comme à MH), /* Tente le talent : dépense les PA (moitié remboursée en cas d'échec, comme à MH),
* jette la maîtrise, journalise et ouvre le bloc de détail du combat. * jette la maîtrise, journalise et ouvre le bloc de détail du combat.
* Retourne true si l'effet s'applique ; le bloc reste à flusher par l'appelant. */ * Retourne true si l'effet s'applique ; le bloc reste à flusher par l'appelant. */
function tryTalent(talent, cap, cost, label) { function tryTalent(talent, cap, cost, label, kind) {
if (!spendPA(cost)) return false; if (!spendPA(cost)) return false;
cdStart(label); cdStart(label);
const before = talent.pct; // le jet se compare à la maîtrise d'avant progression const before = talent.pct; // le jet se compare à la maîtrise d'avant progression
@@ -615,7 +645,11 @@ function tryTalent(talent, cap, cost, label) {
log(`${label} : échec (jet ${r.roll}). La moitié des PA est remboursée.${learn}`, "combat"); log(`${label} : échec (jet ${r.roll}). La moitié des PA est remboursée.${learn}`, "combat");
return false; return false;
} }
const pxFlag = kind === "comp" ? "compPXTurn" : "sortPXTurn";
if (!G.troll[pxFlag]) {
gainPX(1); gainPX(1);
G.troll[pxFlag] = true;
}
if (r.gain && talent.pct > before) { if (r.gain && talent.pct > before) {
cdLine(`Vous avez <span class="cd-good">augmenté votre Maîtrise</span> de <span class="cd-val">${talent.pct - before} point(s)</span> (→ ${talent.pct} %).`); cdLine(`Vous avez <span class="cd-good">augmenté votre Maîtrise</span> de <span class="cd-val">${talent.pct - before} point(s)</span> (→ ${talent.pct} %).`);
log(`${label} : réussite (jet ${r.roll}) — maîtrise +${talent.pct - before} % → ${talent.pct} %.`, "good"); log(`${label} : réussite (jet ${r.roll}) — maîtrise +${talent.pct - before} % → ${talent.pct} %.`, "good");
@@ -632,7 +666,7 @@ function useComp() {
if (t.compUsed) { log("Botte Secrète déjà utilisée cette DLA.", "info"); return; } if (t.compUsed) { log("Botte Secrète déjà utilisée cette DLA.", "info"); return; }
const m = adjacentMonster(); const m = adjacentMonster();
if (!m) { log("Aucun monstre adjacent.", "info"); return; } if (!m) { log("Aucun monstre adjacent.", "info"); return; }
if (!tryTalent(t.comp, 90, comp.cost, "🥋 Botte Secrète")) { cdFlush(); afterAction(); return; } if (!tryTalent(t.comp, 90, comp.cost, "🥋 Botte Secrète", "comp")) { cdFlush(); afterAction(); return; }
t.compUsed = true; t.compUsed = true;
cdLine(`Vous portez une <b>Botte Secrète</b> à <b>${m.name}</b>.`); cdLine(`Vous portez une <b>Botte Secrète</b> à <b>${m.name}</b>.`);
const pseudo = { att: Math.max(1, Math.floor(t.att * 2 / 3)), deg: Math.max(1, Math.floor(t.att / 2)), degBonus: 0 }; const pseudo = { att: Math.max(1, Math.floor(t.att * 2 / 3)), deg: Math.max(1, Math.floor(t.att / 2)), degBonus: 0 };
@@ -642,19 +676,19 @@ function useComp() {
m.pv -= r.damage; m.pv -= r.damage;
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>.`);
log(`Botte Secrète : ${r.attRoll} vs ${r.esqRoll}${r.damage} dégâts !`, "combat"); log(`Botte Secrète : ${r.attRoll} vs ${r.esqRoll}${r.damage} dégâts !`, "combat");
if (m.pv <= 0) killMonster(m); if (m.pv <= 0) killMonster(m, "comp");
} else { } else {
log(`Botte Secrète esquivée (${r.attRoll} vs ${r.esqRoll}).`, "combat"); log(`Botte Secrète esquivée (${r.attRoll} vs ${r.esqRoll}).`, "combat");
} }
} else if (t.race === "Durakuir") { // Régénération Accrue : 1D3 par tranche de 15 PV max } else if (t.race === "Durakuir") { // Régénération Accrue : 1D3 par tranche de 15 PV max
if (!tryTalent(t.comp, 90, comp.cost, "🥋 Régénération Accrue")) { cdFlush(); afterAction(); return; } if (!tryTalent(t.comp, 90, comp.cost, "🥋 Régénération Accrue", "comp")) { cdFlush(); afterAction(); return; }
const dice = Math.max(1, Math.floor(t.pvMax / 15)); const dice = Math.max(1, Math.floor(t.pvMax / 15));
const heal = rollDice(dice, 3).total; const heal = rollDice(dice, 3).total;
t.pv = Math.min(t.pvMax, t.pv + heal); t.pv = Math.min(t.pvMax, t.pv + heal);
cdLine(`Votre chair se referme : vous récupérez <span class="cd-val">${heal} points de Vie</span> (${dice}D3).`); cdLine(`Votre chair se referme : vous récupérez <span class="cd-val">${heal} points de Vie</span> (${dice}D3).`);
log(`Régénération Accrue : ${dice}D3 → +${heal} PV.`, "good"); log(`Régénération Accrue : ${dice}D3 → +${heal} PV.`, "good");
} else if (t.race === "Kastar") { // Accélération du Métabolisme : PV → PA, fatigue croissante } else if (t.race === "Kastar") { // Accélération du Métabolisme : PV → PA, fatigue croissante
if (!tryTalent(t.comp, 90, comp.cost, "🥋 Accélération du Métabolisme")) { cdFlush(); afterAction(); return; } if (!tryTalent(t.comp, 90, comp.cost, "🥋 Accélération du Métabolisme", "comp")) { cdFlush(); afterAction(); return; }
const cost = rollDice(1, 3).total + t.fatigue; const cost = rollDice(1, 3).total + t.fatigue;
t.fatigue += 1; t.fatigue += 1;
t.pv -= cost; t.pv -= cost;
@@ -664,7 +698,7 @@ function useComp() {
if (t.pv <= 0) { cdClose(); die({ name: "son propre métabolisme" }); return; } if (t.pv <= 0) { cdClose(); die({ name: "son propre métabolisme" }); return; }
} else if (t.race === "Tomawak") { // Camouflage : invisible tant qu'on n'attaque pas } else if (t.race === "Tomawak") { // Camouflage : invisible tant qu'on n'attaque pas
if (t.camo) { log("Tu es déjà camouflé.", "info"); return; } if (t.camo) { log("Tu es déjà camouflé.", "info"); return; }
if (!tryTalent(t.comp, 90, comp.cost, "🥋 Camouflage")) { cdFlush(); afterAction(); return; } if (!tryTalent(t.comp, 90, comp.cost, "🥋 Camouflage", "comp")) { cdFlush(); afterAction(); return; }
t.camo = true; t.camo = true;
cdLine(`Vous vous fondez dans les ombres : <span class="cd-good">invisible</span> tant que vous n'attaquez pas.`); cdLine(`Vous vous fondez dans les ombres : <span class="cd-good">invisible</span> tant que vous n'attaquez pas.`);
log("🌫️ Camouflage : les monstres ne te voient plus. Chaque pas risque de te dévoiler (75 % de la maîtrise).", "good"); log("🌫️ Camouflage : les monstres ne te voient plus. Chaque pas risque de te dévoiler (75 % de la maîtrise).", "good");
@@ -672,7 +706,7 @@ function useComp() {
if (t.compUsed) { log("Balayage déjà utilisé cette DLA.", "info"); return; } if (t.compUsed) { log("Balayage déjà utilisé cette DLA.", "info"); return; }
const m = adjacentMonster(); const m = adjacentMonster();
if (!m) { log("Aucun monstre adjacent.", "info"); return; } if (!m) { log("Aucun monstre adjacent.", "info"); return; }
if (!tryTalent(t.comp, 90, comp.cost, "🥋 Balayage")) { cdFlush(); afterAction(); return; } if (!tryTalent(t.comp, 90, comp.cost, "🥋 Balayage", "comp")) { cdFlush(); afterAction(); return; }
t.compUsed = true; t.compUsed = true;
const destab = rollDice(t.att, 6).total; const destab = rollDice(t.att, 6).total;
const stab = rollDice(Math.max(1, Math.floor(m.esq * 2 / 3)), 6).total; const stab = rollDice(Math.max(1, Math.floor(m.esq * 2 / 3)), 6).total;
@@ -700,7 +734,7 @@ function useSort() {
if (t.race === "Skrim") { // Hypnotisme : esquive /2 + perd son tour if (t.race === "Skrim") { // Hypnotisme : esquive /2 + perd son tour
const m = adjacentMonster(); const m = adjacentMonster();
if (!m) { log("Aucun monstre adjacent.", "info"); return; } if (!m) { log("Aucun monstre adjacent.", "info"); return; }
if (!tryTalent(t.sort, 80, sort.cost, "🔮 Hypnotisme")) { cdFlush(); afterAction(); return; } if (!tryTalent(t.sort, 80, sort.cost, "🔮 Hypnotisme", "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.`);
const res = resistInfo(m); const res = resistInfo(m);
m.esqHalfTurns = (m.esqHalfTurns || 0) + (res ? 1 : 2); m.esqHalfTurns = (m.esqHalfTurns || 0) + (res ? 1 : 2);
@@ -714,7 +748,7 @@ function useSort() {
} else if (t.race === "Durakuir") { // Rafale Psychique : imparable, ignore l'armure } else if (t.race === "Durakuir") { // Rafale Psychique : imparable, ignore l'armure
const m = adjacentMonster(); const m = adjacentMonster();
if (!m) { log("Aucun monstre adjacent.", "info"); return; } if (!m) { log("Aucun monstre adjacent.", "info"); return; }
if (!tryTalent(t.sort, 80, sort.cost, "🔮 Rafale Psychique")) { 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(t.deg, 3).total; let dmg = rollDice(t.deg, 3).total;
@@ -723,11 +757,11 @@ function useSort() {
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 ignorée).`);
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 ignorée.`, "combat");
if (m.pv <= 0) killMonster(m); 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();
if (!m) { log("Aucun monstre adjacent.", "info"); return; } if (!m) { log("Aucun monstre adjacent.", "info"); return; }
if (!tryTalent(t.sort, 80, sort.cost, "🔮 Vampirisme")) { cdFlush(); afterAction(); return; } if (!tryTalent(t.sort, 80, sort.cost, "🔮 Vampirisme", "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.`);
const pseudo = { att: Math.max(1, Math.floor(t.deg * 2 / 3)), deg: t.deg, degBonus: 0 }; const pseudo = { att: Math.max(1, Math.floor(t.deg * 2 / 3)), deg: t.deg, degBonus: 0 };
const r = resolveAttack(pseudo, effMonster(m), { ignoreArmor: true }); const r = resolveAttack(pseudo, effMonster(m), { ignoreArmor: true });
@@ -742,14 +776,14 @@ function useSort() {
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 ignorée).`);
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); if (m.pv <= 0) killMonster(m, "sort");
} else { } else {
log(`Vampirisme esquivé (${r.attRoll} vs ${r.esqRoll}).`, "combat"); log(`Vampirisme esquivé (${r.attRoll} vs ${r.esqRoll}).`, "combat");
} }
} else if (t.race === "Tomawak") { // Projectile Magique : à distance, portée = Vue } else if (t.race === "Tomawak") { // Projectile Magique : à distance, portée = Vue
const m = nearestVisibleMonster(); const m = nearestVisibleMonster();
if (!m) { log("Aucun monstre en vue.", "info"); return; } if (!m) { log("Aucun monstre en vue.", "info"); return; }
if (!tryTalent(t.sort, 80, sort.cost, "🔮 Projectile Magique")) { cdFlush(); afterAction(); return; } if (!tryTalent(t.sort, 80, sort.cost, "🔮 Projectile Magique", "sort")) { cdFlush(); afterAction(); return; }
const dist = Math.max(Math.abs(m.x - t.x), Math.abs(m.y - t.y)); const dist = Math.max(Math.abs(m.x - t.x), Math.abs(m.y - t.y));
const proxBonus = Math.max(0, t.vue - dist); const proxBonus = Math.max(0, t.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).`);
@@ -763,7 +797,7 @@ function useSort() {
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 ignorée).`);
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); if (m.pv <= 0) killMonster(m, "sort");
} else { } else {
log(`Projectile Magique esquivé (${r.attRoll} vs ${r.esqRoll}).`, "combat"); log(`Projectile Magique esquivé (${r.attRoll} vs ${r.esqRoll}).`, "combat");
} }
@@ -776,7 +810,7 @@ function useSort() {
} else if (t.race === "Darkling") { // Siphon des Âmes : dégâts + nécrose (ATT) } else if (t.race === "Darkling") { // Siphon des Âmes : dégâts + nécrose (ATT)
const m = adjacentMonster(); const m = adjacentMonster();
if (!m) { log("Aucun monstre adjacent.", "info"); return; } if (!m) { log("Aucun monstre adjacent.", "info"); return; }
if (!tryTalent(t.sort, 80, sort.cost, "🔮 Siphon des Âmes")) { cdFlush(); afterAction(); return; } if (!tryTalent(t.sort, 80, sort.cost, "🔮 Siphon des Âmes", "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.`);
const pseudo = { att: t.att, deg: t.reg, degBonus: 0 }; const pseudo = { att: t.att, deg: t.reg, degBonus: 0 };
const r = resolveAttack(pseudo, effMonster(m), { ignoreArmor: true }); const r = resolveAttack(pseudo, effMonster(m), { ignoreArmor: true });
@@ -792,7 +826,7 @@ function useSort() {
cdLine(`Vous lui avez infligé <span class="cd-val">${dmg} points de dégâts</span> (toute armure ignorée).`); cdLine(`Vous lui avez infligé <span class="cd-val">${dmg} points de dégâts</span> (toute armure ignorée).`);
cdLine(`La nécrose lui fait perdre <span class="cd-val">${necrose} dé(s) d'Attaque</span> pendant 2 tours.`); cdLine(`La nécrose lui fait perdre <span class="cd-val">${necrose} dé(s) d'Attaque</span> pendant 2 tours.`);
log(`Siphon des Âmes : ${dmg} dégâts${res ? " (résisté)" : ""}, nécrose ${necrose} dé(s) d'ATT pendant 2 tours.`, "combat"); log(`Siphon des Âmes : ${dmg} dégâts${res ? " (résisté)" : ""}, nécrose ${necrose} dé(s) d'ATT pendant 2 tours.`, "combat");
if (m.pv <= 0) killMonster(m); if (m.pv <= 0) killMonster(m, "sort");
} else { } else {
log(`Siphon des Âmes esquivé (${r.attRoll} vs ${r.esqRoll}).`, "combat"); log(`Siphon des Âmes esquivé (${r.attRoll} vs ${r.esqRoll}).`, "combat");
} }
@@ -950,6 +984,8 @@ function passDLA() {
t.pa = PA_PER_TURN; t.pa = PA_PER_TURN;
t.compUsed = false; t.compUsed = false;
t.compPXTurn = false;
t.sortPXTurn = false;
t.fatigue = Math.floor(t.fatigue / 1.25); // la fatigue du Kastar retombe à chaque DLA t.fatigue = Math.floor(t.fatigue / 1.25); // la fatigue du Kastar retombe à chaque DLA
afterAction(); afterAction();
} }
@@ -1272,7 +1308,7 @@ if (typeof document !== "undefined") {
/* Export pour les tests node */ /* Export pour les tests node */
if (typeof module !== "undefined" && module.exports) { if (typeof module !== "undefined" && module.exports) {
module.exports = { module.exports = {
rollDice, resolveAttack, resolveSpell, masteryRoll, improveCost, levelFromTotalPI, rollDice, resolveAttack, resolveSpell, masteryRoll, improveCost, levelFromTotalPI, killPX,
RACES, MONSTER_TYPES, BOSS, TEMPLATES, makeMonster, monsterFromSpec, itemFromSpec, RACES, MONSTER_TYPES, BOSS, TEMPLATES, makeMonster, monsterFromSpec, itemFromSpec,
generateCavern, largestRegion, generateCavern, largestRegion,
MAP_W, MAP_H, T_WALL, T_FLOOR, T_STAIRS, MAP_W, MAP_H, T_WALL, T_FLOOR, T_STAIRS,
+6
View File
@@ -89,6 +89,12 @@ assert.strictEqual(g.levelFromTotalPI(20), 2);
assert.strictEqual(g.levelFromTotalPI(49), 2); assert.strictEqual(g.levelFromTotalPI(49), 2);
assert.strictEqual(g.levelFromTotalPI(50), 3); assert.strictEqual(g.levelFromTotalPI(50), 3);
// PX de mise à mort : 10 + 2 × (niveau cible niveau troll) + niveau cible
assert.strictEqual(g.killPX(1, 5), 23, "troll niv 1 vs cible niv 5");
assert.strictEqual(g.killPX(5, 5), 15, "même niveau");
assert.strictEqual(g.killPX(60, 1), -107, "troll niv 60 vs cible niv 1 → négatif");
assert.strictEqual(g.killPX(3, 1), 7, "troll niv 3 vs cible niv 1");
// Les 5 races existent avec stats officielles, compétence et sortilège réservés // Les 5 races existent avec stats officielles, compétence et sortilège réservés
assert.strictEqual(Object.keys(g.RACES).length, 5); assert.strictEqual(Object.keys(g.RACES).length, 5);
for (const r of Object.values(g.RACES)) { for (const r of Object.values(g.RACES)) {