1.3.1 : coûts d'amélioration officiels (Rules_3.php) + armure naturelle
Table officielle : N-ième achat = N × base, caractéristique favorite à 12 PI, REG à 30 (Darkling 22), PV par tranche de 10 à 16 (Durakuir 12), et armure naturelle en D3 achetable à 30 PI pour toutes les races (réduction de dégâts jetée en D3 en plus de l'équipement). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,7 @@ 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 | Identique, avec le détail des jets dans le journal |
|
| Combat | Somme ATT D6 vs somme ESQ D6, dégâts DEG D3 − armure | Identique, avec le détail des jets dans le 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), plafonds 90 %/80 %, é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), plafonds 90 %/80 %, é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é |
|
||||||
| Progression | PX → PI à l'entraînement, coût (dés + 1) × coût racial | Identique, avec réduction sur la caractéristique favorite de la race |
|
| 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) |
|
||||||
| Coûts en PA | Déplacement 1–3, attaque, ramasser, équiper, potion… | Déplacement 1, attaque 3, ramasser 1, équiper 2, potion 1 |
|
| Coûts en PA | Déplacement 1–3, attaque, ramasser, équiper, potion… | Déplacement 1, attaque 3, ramasser 1, équiper 2, potion 1 |
|
||||||
@@ -61,8 +61,22 @@ Profils de base repris des pages officielles, compétence (🥋 2 PA) et sortil
|
|||||||
| 🟡 Tomawak | ATT 3D6 · ESQ 3D6 · DEG 3D3 · REG 1D3 · PV 30 · Vue 4 | **Camouflage** : invisible, jet sous 75 % de la maîtrise à chaque pas | **Projectile Magique** : à distance (portée = Vue), bonus de proximité |
|
| 🟡 Tomawak | ATT 3D6 · ESQ 3D6 · DEG 3D3 · REG 1D3 · PV 30 · Vue 4 | **Camouflage** : invisible, jet sous 75 % de la maîtrise à chaque pas | **Projectile Magique** : à distance (portée = Vue), bonus de proximité |
|
||||||
| 🟣 Darkling | ATT 3D6 · ESQ 3D6 · DEG 3D3 · REG 2D3 · PV 30 · Vue 3 | **Balayage** : déstabilise (la cible perd son tour) | **Siphon des Âmes** : 1D3 par dé de REG, ignore toute armure, nécrose −ATT |
|
| 🟣 Darkling | ATT 3D6 · ESQ 3D6 · DEG 3D3 · REG 2D3 · PV 30 · Vue 3 | **Balayage** : déstabilise (la cible perd son tour) | **Siphon des Âmes** : 1D3 par dé de REG, ignore toute armure, nécrose −ATT |
|
||||||
|
|
||||||
Le coût en PI reste réduit sur la caractéristique favorite de chaque race
|
### Coûts d'amélioration (officiels, [Rules_3.php](https://www.mountyhall.com/MH_Rules/Rules_3.php))
|
||||||
(ATT Skrim, PV Durakuir, DEG Kastar, Vue Tomawak, REG Darkling).
|
|
||||||
|
Le N-ième achat d'une caractéristique coûte N × le coût de base :
|
||||||
|
|
||||||
|
| Amélioration | Skrim | Durakuir | Kastar | Tomawak | Darkling |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| +1D6 Attaque | **12** | 16 | 16 | 16 | 16 |
|
||||||
|
| +1D6 Esquive | 16 | 16 | 16 | 16 | 16 |
|
||||||
|
| +1D3 Dégâts | 16 | 16 | **12** | 16 | 16 |
|
||||||
|
| +1D3 Régénération | 30 | 30 | 30 | 30 | **22** |
|
||||||
|
| +1D3 Armure naturelle | 30 | 30 | 30 | 30 | 30 |
|
||||||
|
| +1 Vue | 16 | 16 | 16 | **12** | 16 |
|
||||||
|
| +10 PV | 16 | **12** | 16 | 16 | 16 |
|
||||||
|
|
||||||
|
(L'amélioration « 30 minutes de DLA » du jeu original n'a pas d'équivalent ici,
|
||||||
|
la DLA étant instantanée.)
|
||||||
|
|
||||||
## Contrôles
|
## Contrôles
|
||||||
|
|
||||||
@@ -128,6 +142,9 @@ non affilié au jeu original de Mountyhall SARL.
|
|||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
|
- **1.3.1** (2026-06-11) — Coûts d'amélioration officiels de Rules_3.php (N-ième
|
||||||
|
achat = N × base, favorisée 12 PI, REG 30/22, PV par 10) et armure naturelle
|
||||||
|
en D3 achetable (30 PI pour toutes les races).
|
||||||
- **1.3.0** (2026-06-11) — Races fidèles aux règles officielles : profils de base
|
- **1.3.0** (2026-06-11) — Races fidèles aux règles officielles : profils de base
|
||||||
exacts, dégâts en D3, compétence + sortilège réservés par race avec maîtrise en %
|
exacts, dégâts en D3, compétence + sortilège réservés par race avec maîtrise en %
|
||||||
progressant à l'usage, jets de résistance des monstres, statuts (à terre, hypnose,
|
progressant à l'usage, jets de résistance des monstres, statuts (à terre, hypnose,
|
||||||
|
|||||||
+23
-17
@@ -26,7 +26,9 @@ function resolveAttack(attacker, defender, opts = {}) {
|
|||||||
if (result.hit) {
|
if (result.hit) {
|
||||||
const deg = rollDice(attacker.deg, 3);
|
const deg = rollDice(attacker.deg, 3);
|
||||||
result.rawDamage = deg.total + (attacker.degBonus || 0);
|
result.rawDamage = deg.total + (attacker.degBonus || 0);
|
||||||
const armor = opts.ignoreArmor ? 0 : (defender.armor || 0);
|
// 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);
|
||||||
result.damage = Math.max(1, result.rawDamage - armor);
|
result.damage = Math.max(1, result.rawDamage - armor);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -53,14 +55,17 @@ function resolveSpell(mm, rm) {
|
|||||||
return { sr, roll, success: roll <= sr };
|
return { sr, roll, success: roll <= sr };
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Coût en PI du prochain dé d'une caractéristique : (dés actuels + 1) × coût de base,
|
/* Coûts d'amélioration officiels (MH_Rules/Rules_3.php) : le N-ième achat d'une
|
||||||
* réduit pour la caractéristique favorite de la race (cf. règles MH). */
|
* caractéristique coûte N × coût de base de la race. La caractéristique favorite
|
||||||
function improveCost(stat, currentValue, race) {
|
* est à 12 PI (REG du Darkling : 22), le reste à 16, REG à 30, Armure à 30.
|
||||||
const base = { att: 18, esq: 18, deg: 18, reg: 15, pv: 10, vue: 15 };
|
* `bought` = nombre d'améliorations déjà achetées dans cette caractéristique. */
|
||||||
|
const IMPROVE_BASE = { att: 16, esq: 16, deg: 16, reg: 30, pv: 16, vue: 16, armor: 30 };
|
||||||
|
const IMPROVE_FAVORED = { att: 12, deg: 12, pv: 12, vue: 12, reg: 22 };
|
||||||
|
|
||||||
|
function improveCost(stat, bought, race) {
|
||||||
const favored = RACES[race].favored === stat;
|
const favored = RACES[race].favored === stat;
|
||||||
const mult = favored ? Math.ceil(base[stat] * 2 / 3) : base[stat];
|
const base = favored ? IMPROVE_FAVORED[stat] : IMPROVE_BASE[stat];
|
||||||
if (stat === "pv") return mult; // +5 PV, coût fixe
|
return (bought + 1) * base;
|
||||||
return (currentValue + 1) * mult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Niveau d'ancienneté : passer au niveau N coûte 10×N PI gagnés (cumulés). */
|
/* Niveau d'ancienneté : passer au niveau N coûte 10×N PI gagnés (cumulés). */
|
||||||
@@ -292,7 +297,8 @@ function newGame(name, race, customLevel = null) {
|
|||||||
name, race,
|
name, race,
|
||||||
att: s.att, esq: s.esq, deg: s.deg, reg: s.reg,
|
att: s.att, esq: s.esq, deg: s.deg, reg: s.reg,
|
||||||
pv: s.pvMax, pvMax: s.pvMax, vue: s.vue,
|
pv: s.pvMax, pvMax: s.pvMax, vue: s.vue,
|
||||||
degBonus: 0, armor: 0,
|
degBonus: 0, armor: 0, armorDice: 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,
|
||||||
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,
|
||||||
@@ -744,13 +750,14 @@ function descend() {
|
|||||||
|
|
||||||
function improveStat(stat) {
|
function improveStat(stat) {
|
||||||
const t = G.troll;
|
const t = G.troll;
|
||||||
const current = stat === "pv" ? t.pvMax : t[stat];
|
const cost = improveCost(stat, t.bought[stat], t.race);
|
||||||
const cost = improveCost(stat, stat === "pv" ? 0 : current, t.race);
|
|
||||||
if (t.pi < cost) return;
|
if (t.pi < cost) return;
|
||||||
t.pi -= cost;
|
t.pi -= cost;
|
||||||
if (stat === "pv") { t.pvMax += 5; t.pv += 5; }
|
t.bought[stat] += 1;
|
||||||
|
if (stat === "pv") { t.pvMax += 10; t.pv += 10; }
|
||||||
|
else if (stat === "armor") t.armorDice += 1;
|
||||||
else t[stat] += 1;
|
else t[stat] += 1;
|
||||||
const label = { att: "Attaque", esq: "Esquive", deg: "Dégâts", reg: "Régénération", pv: "PV max", vue: "Vue" }[stat];
|
const label = { att: "Attaque", esq: "Esquive", deg: "Dégâts", reg: "Régénération", pv: "PV max", vue: "Vue", armor: "Armure naturelle" }[stat];
|
||||||
log(`📈 Entraînement : ${label} améliorée pour ${cost} PI.`, "good");
|
log(`📈 Entraînement : ${label} améliorée pour ${cost} PI.`, "good");
|
||||||
if (stat === "vue") updateFov();
|
if (stat === "vue") updateFov();
|
||||||
afterAction();
|
afterAction();
|
||||||
@@ -945,7 +952,7 @@ function renderPanels() {
|
|||||||
<div><span>Esquive</span><span class="stat-val">${t.esq}D6</span></div>
|
<div><span>Esquive</span><span class="stat-val">${t.esq}D6</span></div>
|
||||||
<div><span>Dégâts</span><span class="stat-val">${t.deg}D3${t.degBonus ? "+" + t.degBonus : ""}</span></div>
|
<div><span>Dégâts</span><span class="stat-val">${t.deg}D3${t.degBonus ? "+" + t.degBonus : ""}</span></div>
|
||||||
<div><span>Régénération</span><span class="stat-val">${t.reg}D3</span></div>
|
<div><span>Régénération</span><span class="stat-val">${t.reg}D3</span></div>
|
||||||
<div><span>Armure</span><span class="stat-val">${t.armor}</span></div>
|
<div><span>Armure</span><span class="stat-val">${t.armor}${t.armorDice ? "+" + t.armorDice + "D3" : ""}</span></div>
|
||||||
<div><span>Vue</span><span class="stat-val">${t.vue}</span></div>
|
<div><span>Vue</span><span class="stat-val">${t.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>
|
||||||
@@ -957,10 +964,9 @@ 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 +5", vue: "Vue +1" };
|
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" };
|
||||||
for (const stat of Object.keys(labels)) {
|
for (const stat of Object.keys(labels)) {
|
||||||
const current = stat === "pv" ? 0 : t[stat];
|
const cost = improveCost(stat, t.bought[stat], t.race);
|
||||||
const cost = improveCost(stat, current, t.race);
|
|
||||||
const btn = document.createElement("button");
|
const btn = document.createElement("button");
|
||||||
const fav = RACES[t.race].favored === stat ? " ★" : "";
|
const fav = RACES[t.race].favored === stat ? " ★" : "";
|
||||||
btn.textContent = `${labels[stat]} — ${cost} PI${fav}`;
|
btn.textContent = `${labels[stat]} — ${cost} PI${fav}`;
|
||||||
|
|||||||
+21
-4
@@ -42,10 +42,27 @@ for (let i = 0; i < 50; i++) {
|
|||||||
assert.strictEqual(g.resolveSpell(100, 0).sr, 90);
|
assert.strictEqual(g.resolveSpell(100, 0).sr, 90);
|
||||||
assert.strictEqual(g.resolveSpell(0, 100).sr, 10);
|
assert.strictEqual(g.resolveSpell(0, 100).sr, 10);
|
||||||
|
|
||||||
// Coûts d'amélioration : la race favorisée paie moins cher
|
// Coûts d'amélioration officiels (Rules_3.php) : N-ième achat = N × coût de base
|
||||||
const skrimAtt = g.improveCost("att", 3, "Skrim");
|
assert.strictEqual(g.improveCost("att", 0, "Skrim"), 12, "1er dé d'ATT Skrim = 12 PI");
|
||||||
const kastarAtt = g.improveCost("att", 3, "Kastar");
|
assert.strictEqual(g.improveCost("att", 2, "Skrim"), 36, "3e dé d'ATT Skrim = 36 PI (exemple officiel)");
|
||||||
assert(skrimAtt < kastarAtt, `Skrim devrait payer son ATT moins cher (${skrimAtt} vs ${kastarAtt})`);
|
assert.strictEqual(g.improveCost("att", 0, "Kastar"), 16, "ATT non favorisée = 16 PI");
|
||||||
|
assert.strictEqual(g.improveCost("deg", 0, "Kastar"), 12, "DEG favorisé Kastar = 12 PI");
|
||||||
|
assert.strictEqual(g.improveCost("pv", 0, "Durakuir"), 12, "PV favorisés Durakuir = 12 PI");
|
||||||
|
assert.strictEqual(g.improveCost("vue", 0, "Tomawak"), 12, "Vue favorisée Tomawak = 12 PI");
|
||||||
|
assert.strictEqual(g.improveCost("reg", 0, "Darkling"), 22, "REG favorisée Darkling = 22 PI");
|
||||||
|
assert.strictEqual(g.improveCost("reg", 0, "Skrim"), 30, "REG non favorisée = 30 PI");
|
||||||
|
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");
|
||||||
|
|
||||||
|
// L'armure naturelle en D3 réduit bien les dégâts
|
||||||
|
{
|
||||||
|
let withArmor = 0, without = 0;
|
||||||
|
for (let i = 0; i < 400; i++) {
|
||||||
|
withArmor += g.resolveAttack({ att: 10, deg: 5 }, { esq: 1, armor: 0, armorDice: 4 }).damage;
|
||||||
|
without += g.resolveAttack({ att: 10, deg: 5 }, { esq: 1, armor: 0 }).damage;
|
||||||
|
}
|
||||||
|
assert(withArmor < without, "4D3 d'armure naturelle doit réduire les dégâts");
|
||||||
|
}
|
||||||
|
|
||||||
// Niveaux : 0 PI → niv 1 ; 20 PI → niv 2 ; 50 PI → niv 3 (20 + 30)
|
// Niveaux : 0 PI → niv 1 ; 20 PI → niv 2 ; 50 PI → niv 3 (20 + 30)
|
||||||
assert.strictEqual(g.levelFromTotalPI(0), 1);
|
assert.strictEqual(g.levelFromTotalPI(0), 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user