1.5.0 : page « Règles des talents » au format fiches MountyHall

Les 10 fiches compétences/sortilèges (coût en PA, type, jet de
résistance, effet de zone, limite par DLA, formules de jets, lore),
accessibles depuis l'accueil et en jeu. En partie, encart « Mes infos »
par talent de la race jouée : maîtrise actuelle et plafond, taux de
réussite sur les jets effectués, dernière utilisation et jets calculés
du troll. tryTalent compte désormais essais/réussites/dernier usage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
perco
2026-06-11 14:42:12 +02:00
co-authored by Claude Fable 5
parent bffe9f9f75
commit 92df54fca2
5 changed files with 355 additions and 1 deletions
+14
View File
@@ -61,6 +61,16 @@ 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 |
### Page « Règles des talents »
Accessible depuis l'accueil (📜 Règles des talents) et en jeu (bouton 📜 Règles
dans l'en-tête) : les 10 fiches au format MountyHall — Coût en PA, Type, Jet de
résistance, Effet de zone, limite par DLA, EFFET détaillé avec les formules de
jets, et DESCRIPTION d'ambiance. En cours de partie, la fiche des talents de ta
race affiche en plus un encart « **Mes infos** » : pourcentage actuel et plafond,
taux de réussite sur tes jets, dernière utilisation, et tes jets calculés
(ex. Vampirisme : Attaque 2 D6 · Dégâts 4 D3 · Vol de vie 50 %).
### Coûts d'amélioration (officiels, [Rules_3.php](https://www.mountyhall.com/MH_Rules/Rules_3.php)) ### Coûts d'amélioration (officiels, [Rules_3.php](https://www.mountyhall.com/MH_Rules/Rules_3.php))
Le N-ième achat d'une caractéristique coûte N × le coût de base : Le N-ième achat d'une caractéristique coûte N × le coût de base :
@@ -142,6 +152,10 @@ non affilié au jeu original de Mountyhall SARL.
## Versions ## Versions
- **1.5.0** (2026-06-11) — Page « Règles des talents » : les 10 fiches compétences/
sortilèges au format MountyHall (coût, type, jet de résistance, formules, lore),
avec encart « Mes infos » en partie (maîtrise, taux de réussite, jets calculés du
troll). Suivi des statistiques d'utilisation des talents.
- **1.4.0** (2026-06-11) — Panneau « Détail du combat » au format des rapports - **1.4.0** (2026-06-11) — Panneau « Détail du combat » au format des rapports
MountyHall (jets d'Attaque/Esquive, coup critique, Seuil et jet de Résistance, MountyHall (jets d'Attaque/Esquive, coup critique, Seuil et jet de Résistance,
progression de Maîtrise, dégâts, mise à mort, butin, total de PX), pour chaque progression de Maîtrise, dégâts, mise à mort, butin, total de PX), pour chaque
+65
View File
@@ -254,6 +254,71 @@ header h1 { font-size: 1.3em; color: #8fbf5a; }
.ed-warn { color: #e06060; } .ed-warn { color: #e06060; }
.ed-ok { color: #8fbf5a; } .ed-ok { color: #8fbf5a; }
.ed-note { font-size: .8em; color: #a89568; } .ed-note { font-size: .8em; color: #a89568; }
/* ---------- Règles des talents ---------- */
.header-right { display: flex; align-items: center; gap: 12px; }
#rules-body {
max-width: 1100px;
margin: 0 auto;
padding: 10px 16px 40px;
}
.rules-race {
color: #8fbf5a;
border-bottom: 2px solid #4a3a20;
margin: 24px 0 12px;
font-size: 1.4em;
}
.rules-pair {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
gap: 14px;
}
.rules-fiche {
background: #2a2014;
border: 2px solid #6b5430;
border-radius: 8px;
padding: 14px;
font-size: .92em;
}
.rules-fiche-title {
color: #d8a040;
font-weight: bold;
font-size: 1.1em;
text-align: center;
border-bottom: 1px solid #4a3a20;
padding-bottom: 6px;
margin-bottom: 8px;
}
.rules-fiche table { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
.rules-fiche td { padding: 2px 6px; vertical-align: top; }
.rules-fiche td:first-child { color: #a89568; width: 45%; }
.rules-section {
color: #8fbf5a;
font-weight: bold;
margin: 10px 0 4px;
letter-spacing: 1px;
}
.rules-text { color: #d8c79a; line-height: 1.5; }
.rules-flavor { font-style: italic; color: #b9a878; }
.rules-myinfo {
background: #1f2a14;
border: 2px solid #5d8535;
border-radius: 8px;
padding: 10px;
margin-bottom: 12px;
}
.rules-myinfo-title {
color: #8fbf5a;
font-weight: bold;
text-align: center;
border-bottom: 1px solid #41551f;
padding-bottom: 4px;
margin-bottom: 6px;
}
.rules-myinfo table { width: 100%; }
.rules-myinfo td { padding: 2px 6px; }
.rules-myinfo td:first-child { color: #a89568; width: 45%; }
#ed-help { max-width: 600px; margin: 0 auto; } #ed-help { max-width: 600px; margin: 0 auto; }
#equipment div, #inventory .empty { font-size: .88em; padding: 2px 0; color: #c9b685; } #equipment div, #inventory .empty { font-size: .88em; padding: 2px 0; color: #c9b685; }
+15 -1
View File
@@ -22,6 +22,7 @@
<div class="menu-row"> <div class="menu-row">
<button id="btn-community" class="menu-btn">🌍 Niveaux de la communauté</button> <button id="btn-community" class="menu-btn">🌍 Niveaux de la communauté</button>
<button id="btn-editor" class="menu-btn">🛠️ Éditeur de niveaux</button> <button id="btn-editor" class="menu-btn">🛠️ Éditeur de niveaux</button>
<button id="btn-rules" class="menu-btn">📜 Règles des talents</button>
</div> </div>
</div> </div>
</div> </div>
@@ -83,7 +84,10 @@
<div id="screen-game" class="screen hidden"> <div id="screen-game" class="screen hidden">
<header> <header>
<h1>🧌 MountyCrawl</h1> <h1>🧌 MountyCrawl</h1>
<div id="depth-label">Profondeur 1</div> <div class="header-right">
<div id="depth-label">Profondeur 1</div>
<button id="btn-rules-game" class="menu-btn">📜 Règles</button>
</div>
</header> </header>
<div id="layout"> <div id="layout">
<aside id="panel-left"> <aside id="panel-left">
@@ -120,6 +124,15 @@
</div> </div>
</div> </div>
<!-- Écran des règles des talents -->
<div id="screen-rules" class="screen hidden">
<header>
<h1>📜 Règles des talents</h1>
<button id="rules-back" class="menu-btn">← Retour</button>
</header>
<div id="rules-body"></div>
</div>
<!-- Écran de fin --> <!-- Écran de fin -->
<div id="screen-end" class="screen hidden"> <div id="screen-end" class="screen hidden">
<div class="create-box"> <div class="create-box">
@@ -131,5 +144,6 @@
<script src="js/game.js"></script> <script src="js/game.js"></script>
<script src="js/editor.js"></script> <script src="js/editor.js"></script>
<script src="js/rules.js"></script>
</body> </body>
</html> </html>
+3
View File
@@ -603,6 +603,9 @@ function tryTalent(talent, cap, cost, label) {
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
const r = masteryRoll(talent, cap); const r = masteryRoll(talent, cap);
talent.tries = (talent.tries || 0) + 1;
if (r.success) talent.successes = (talent.successes || 0) + 1;
talent.lastUse = Date.now();
cdLine(`Jet de maîtrise : <span class="cd-val">${r.roll}</span> (il fallait ${before} % ou moins)`); cdLine(`Jet de maîtrise : <span class="cd-val">${r.roll}</span> (il fallait ${before} % ou moins)`);
if (!r.success) { if (!r.success) {
G.troll.pa += Math.floor(cost / 2); G.troll.pa += Math.floor(cost / 2);
+258
View File
@@ -0,0 +1,258 @@
/* Page « Règles des talents » : fiches des compétences et sortilèges par race,
* au format des fiches MountyHall, avec la section « Mes infos » du troll en cours.
* Partage les globales de game.js (chargé avant). */
"use strict";
const TALENT_RULES = {
"Botte Secrète": {
race: "Skrim", kind: "Compétence", icon: "🥋", type: "Attaque",
resist: false, zone: false, perDla: true,
effect: `La Botte Secrète permet de porter, <b>une fois par DLA</b>, une attaque
supplémentaire à moindre coût en PA.<br><br>
L'attaque par Botte Secrète a les caractéristiques suivantes :<br>
• Jet d'Attaque : <b>2 D6 par 3 D6 d'Attaque</b><br>
• Jet de Dégâts : <b>1 D3 par 2 D6 d'Attaque</b><br><br>
Les dégâts sont réduits normalement par l'armure de la cible.`,
flavor: `Le Skrim feinte, virevolte, et place le coup que personne n'a vu venir.
Les Maîtres d'Armes du Hall jurent qu'il est impossible de parer une botte
qu'on ne connaît pas — c'est exactement l'idée.`,
myInfo: t => [
["Attaque", `${Math.max(1, Math.floor(t.att * 2 / 3))} D6`],
["Dégâts", `${Math.max(1, Math.floor(t.att / 2))} D3`],
],
},
"Hypnotisme": {
race: "Skrim", kind: "Sortilège", icon: "🔮", type: "Affaiblissement",
resist: true, zone: false, perDla: false,
effect: `L'Hypnotisme affaiblit un adversaire adjacent :<br>
• son <b>Esquive est divisée par 2</b> pendant 2 tours ;<br>
• il <b>perd son prochain tour</b>.<br><br>
Un Jet de Résistance réussi de la part de la cible réduit l'effet :
l'esquive n'est divisée que pendant 1 tour et le tour n'est pas perdu.`,
flavor: `Les yeux du Skrim se mettent à tournoyer, sa voix se fait sirupeuse…
et l'adversaire se demande soudain ce qu'il était venu faire dans ces cavernes.`,
myInfo: () => [],
},
"Régénération Accrue": {
race: "Durakuir", kind: "Compétence", icon: "🥋", type: "Soin",
resist: false, zone: false, perDla: false,
effect: `Le Durakuir récupère immédiatement des Points de Vie :<br>
• Soin : <b>1 D3 par tranche de 15 PV max</b>.<br><br>
Le calcul prend en compte les PV maximum avec tous leurs bonus.`,
flavor: `La peau du Durakuir se referme à vue d'œil, les os se ressoudent dans
un craquement sinistre. Ce qui ne le tue pas le laisse à peu près indifférent.`,
myInfo: t => [
["Soin", `${Math.max(1, Math.floor(t.pvMax / 15))} D3`],
],
},
"Rafale Psychique": {
race: "Durakuir", kind: "Sortilège", icon: "🔮", type: "Attaque",
resist: true, zone: false, perDla: false,
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>
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
pure mauvaise humeur traverse le crâne de son adversaire.`,
myInfo: t => [
["Attaque", "automatiquement réussie"],
["Dégâts", `${t.deg} D3`],
],
},
"Accélération du Métabolisme": {
race: "Kastar", kind: "Compétence", icon: "🥋", type: "Utilitaire",
resist: false, zone: false, perDla: false,
effect: `Le Kastar sacrifie des Points de Vie pour agir davantage :<br>
• Coût : <b>1 D3 PV + la fatigue accumulée</b> ;<br>
• Gain : <b>+4 PA</b> (sans dépasser 6) ;<br>
• chaque usage augmente la <b>fatigue de 1</b> ; elle se dissipe à chaque
nouvelle DLA (division par 1,25).<br><br>
Attention : si les PV tombent à 0, le métabolisme a raison du Kastar.`,
flavor: `Le cœur du Kastar s'emballe, ses veines gonflent, le monde ralentit
autour de lui. La Faim paiera l'addition plus tard.`,
myInfo: t => [
["Coût actuel", `1 D3 + ${t.fatigue} PV (fatigue)`],
["Gain", "+4 PA"],
],
},
"Vampirisme": {
race: "Kastar", kind: "Sortilège", icon: "🔮", type: "Attaque",
resist: true, zone: false, perDla: false,
effect: `Le Vampirisme permet une attaque magique particulièrement dévastatrice
pour la victime mais bénéfique pour l'assaillant : ce dernier gagne autant de
PV que la <b>moitié des dégâts réellement infligés</b>.<br><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 de Dégâts : <b>1 D3 par D3 de Dégâts</b> ;<br>
• l'<b>armure physique est ignorée</b> ;<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,
le gain est limité à la moitié des PV qu'il lui restait.`,
flavor: `Le Kastar plante ses dents dans son adversaire et lui arrache un bon
morceau. Mais ce n'est pas tout ! Faisant appel aux Enseignements du Dieu du
Manger, le Kastar digère sans mal ce qu'il a pris à son adversaire et sait
profiter de cet apport pour régénérer sans problème.`,
myInfo: t => [
["Attaque", `${Math.max(1, Math.floor(t.deg * 2 / 3))} D6`],
["Dégâts", `${t.deg} D3`],
["Vol de vie", "50 % des dégâts infligés"],
],
},
"Camouflage": {
race: "Tomawak", kind: "Compétence", icon: "🥋", type: "Furtivité",
resist: false, zone: false, perDla: false,
effect: `Le Tomawak devient <b>invisible</b> aux yeux des monstres.<br><br>
• Le camouflage persiste de DLA en DLA tant qu'il n'est pas rompu ;<br>
• à chaque déplacement, un jet sous <b>75 % de la maîtrise</b> est requis
pour rester caché ;<br>
• attaquer rompt le camouflage ; le Projectile Magique ne le conserve
que sur un jet sous <b>25 % de la maîtrise</b>.`,
flavor: `Un frémissement d'air, une ombre qui glisse le long de la paroi…
Le Tomawak n'est plus là. Enfin si, mais bonne chance pour le prouver.`,
myInfo: t => [
["Jet de discrétion au déplacement", `${Math.floor(t.comp.pct * 0.75)} %`],
["Conservation après Projectile Magique", `${Math.floor(t.comp.pct * 0.25)} %`],
],
},
"Projectile Magique": {
race: "Tomawak", kind: "Sortilège", icon: "🔮", type: "Attaque à distance",
resist: true, zone: false, perDla: false,
effect: `Le Projectile Magique frappe à distance, dans la limite de la
<b>portée = Vue</b> du lanceur :<br>
• 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>
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
plein museau de la cible. Le Tomawak, lui, est déjà ailleurs.`,
myInfo: t => [
["Portée", `${t.vue} case(s)`],
["Attaque", `${t.vue} D6 + 1 D6 par case de proximité`],
["Dégâts", `${Math.max(1, Math.floor(t.vue / 2))} D3`],
],
},
"Balayage": {
race: "Darkling", kind: "Compétence", icon: "🥋", type: "Contrôle",
resist: false, zone: false, perDla: true,
effect: `Le Balayage fauche les jambes d'un adversaire adjacent,
<b>une fois par DLA</b> :<br>
• Jet de Déstabilisation : <b>1 D6 par D6 d'Attaque</b> ;<br>
• Jet de Stabilité de la cible : <b>2 D6 par 3 D6 d'Esquive</b> ;<br>
• si la déstabilisation l'emporte, la cible est <b>à terre</b> et
<b>perd son prochain tour</b>.<br><br>
Le Balayage n'inflige pas de dégâts et ne rompt pas la discrétion.`,
flavor: `D'un mouvement fluide et sournois — sa spécialité — le Darkling fauche
les appuis de son adversaire, qui découvre le plafond de la caverne.`,
myInfo: t => [
["Déstabilisation", `${t.att} D6`],
],
},
"Siphon des Âmes": {
race: "Darkling", kind: "Sortilège", icon: "🔮", type: "Attaque",
resist: true, zone: false, perDla: false,
effect: `Le Siphon des Âmes arrache des lambeaux d'essence vitale :<br>
• Jet d'Attaque : <b>1 D6 par D6 d'Attaque</b> ;<br>
• Jet de Dégâts : <b>1 D3 par D3 de Régénération</b> ;<br>
• <b>toute armure est ignorée</b> (physique et magique) ;<br>
• Nécrose : la cible perd <b>1 D6 d'Attaque par D3 de Régénération</b>
du lanceur pendant 2 tours.<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 de la nécrose.`,
flavor: `Des volutes noires s'échappent de la victime et s'enroulent autour du
Darkling, qui les hume comme un grand cru. L'âme a un goût de réglisse, paraît-il.`,
myInfo: t => [
["Attaque", `${t.att} D6`],
["Dégâts", `${t.reg} D3`],
["Nécrose", `${t.reg} D6 d'Attaque pendant 2 tours`],
],
},
};
/* ---------- Rendu de la page ---------- */
let rulesReturnTo = "create"; // écran à réafficher en quittant les règles
function rulesFiche(name, rule, cost) {
const inGame = typeof G !== "undefined" && G && !G.over && G.troll && G.troll.race === rule.race;
let myInfos = "";
if (inGame) {
const t = G.troll;
const talent = rule.kind === "Compétence" ? t.comp : t.sort;
const cap = rule.kind === "Compétence" ? 90 : 80;
const tries = talent.tries || 0;
const rate = tries ? Math.round((talent.successes || 0) * 100 / tries) : 0;
const last = talent.lastUse ? new Date(talent.lastUse).toLocaleString("fr-FR") : "jamais";
const extra = rule.myInfo(t).map(([k, v]) => `<tr><td>${k} :</td><td><b>${v}</b></td></tr>`).join("");
myInfos = `
<div class="rules-myinfo">
<div class="rules-myinfo-title">Mes infos : ${name}</div>
<table>
<tr><td>Pourcentage actuel :</td><td><b>${talent.pct} %</b> (max. : ${cap} %)</td></tr>
<tr><td>Réussite :</td><td><b>${rate} % succès</b> sur ${tries} jet(s)</td></tr>
<tr><td>Dernière utilisation :</td><td>${last}</td></tr>
${extra}
</table>
</div>`;
}
return `
<div class="rules-fiche">
${myInfos}
<div class="rules-fiche-title">${rule.icon} ${rule.kind} : ${name}</div>
<table>
<tr><td>Coût en PA :</td><td><b>${cost}</b></td></tr>
<tr><td>Type :</td><td><b>${rule.type}</b></td></tr>
<tr><td>Jet de résistance :</td><td><b>${rule.resist ? "Oui" : "Non"}</b></td></tr>
<tr><td>Effet de zone :</td><td><b>${rule.zone ? "Oui" : "Non"}</b></td></tr>
<tr><td>Limité à 1 par DLA :</td><td><b>${rule.perDla ? "Oui" : "Non"}</b></td></tr>
</table>
<div class="rules-section">EFFET :</div>
<div class="rules-text">${rule.effect}</div>
<div class="rules-section">DESCRIPTION :</div>
<div class="rules-text rules-flavor">${rule.flavor}</div>
</div>`;
}
function rulesRender() {
const body = document.getElementById("rules-body");
let html = "";
for (const [raceName, race] of Object.entries(RACES)) {
html += `<h2 class="rules-race rules-race-${raceName}">${race.emoji} ${raceName}</h2><div class="rules-pair">`;
html += rulesFiche(race.comp.name, TALENT_RULES[race.comp.name], race.comp.cost);
html += rulesFiche(race.sort.name, TALENT_RULES[race.sort.name], race.sort.cost);
html += "</div>";
}
body.innerHTML = html;
}
function rulesShow(from) {
rulesReturnTo = from;
document.getElementById("screen-" + from).classList.add("hidden");
document.getElementById("screen-rules").classList.remove("hidden");
rulesRender();
document.getElementById("screen-rules").scrollTop = 0;
// si un troll est en jeu, amener sa race en tête de page
if (typeof G !== "undefined" && G && G.troll) {
const anchor = document.querySelector(`#rules-body .rules-race-${G.troll.race}`);
if (anchor) anchor.scrollIntoView();
}
}
if (typeof document !== "undefined") {
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("btn-rules").onclick = () => rulesShow("create");
document.getElementById("btn-rules-game").onclick = () => rulesShow("game");
document.getElementById("rules-back").onclick = () => {
document.getElementById("screen-rules").classList.add("hidden");
document.getElementById("screen-" + rulesReturnTo).classList.remove("hidden");
};
if (new URLSearchParams(location.search).get("screen") === "rules") {
rulesShow(document.getElementById("screen-game").classList.contains("hidden") ? "create" : "game");
}
});
}