diff --git a/css/style.css b/css/style.css index dca11dc..2211aa9 100644 --- a/css/style.css +++ b/css/style.css @@ -169,6 +169,7 @@ header h1 { font-size: 1.3em; color: #8fbf5a; } #stats div { display: flex; justify-content: space-between; padding: 2px 0; } #stats .stat-val { font-family: monospace; color: #f0e6c0; } +#stats .stat-hint { color: #8a7a55; font-size: 0.82em; font-family: Georgia, serif; } #effects-panel { margin-top: 4px; } .fx-empty { color: #8a7a55; font-style: italic; font-size: 0.88em; line-height: 1.4; } @@ -180,6 +181,7 @@ header h1 { font-size: 1.3em; color: #8fbf5a; } margin-bottom: 10px; } .fx-total-title { color: #d8a040; font-size: 0.85em; margin-bottom: 6px; font-weight: bold; } +.fx-total .fx-mod { margin: 0 6px 4px 0; } .fx-card { background: #1f1810; border: 1px solid #4a3a20; diff --git a/js/game.js b/js/game.js index 0ca59ef..7bfd5ba 100644 --- a/js/game.js +++ b/js/game.js @@ -4,7 +4,7 @@ "use strict"; -const APP_VERSION = "0.1.5"; +const APP_VERSION = "0.1.6"; /* Alpha : maîtrise initiale haute pour les tests. Remettre 15 % / 15 % à la v1.0 officielle. */ const START_COMP_PCT = 90; @@ -1187,22 +1187,16 @@ function renderPanels() { $("troll-title").textContent = `${RACES[t.race].emoji} ${t.name}, ${t.race} niv. ${levelFromTotalPI(t.totalPI)}`; const te = effTroll(t); - const fmtDice = (n, faces, flat, extra = 0) => { - let s = `${n}D${faces}`; - const bonus = (flat || 0) + (extra || 0); - if (bonus) s += bonus > 0 ? ` +${bonus}` : ` ${bonus}`; - return s; - }; const pct = Math.max(0, t.pv / t.pvMax); const hpClass = pct > 0.6 ? "high" : pct > 0.3 ? "mid" : ""; $("stats").innerHTML = `
Tour${t.tour}
PV${t.pv} / ${t.pvMax}
-
Attaque${fmtDice(te.att, 6, te.attFlat)}
-
Esquive${fmtDice(te.esq, 6, te.esqFlat)}
-
Dégâts${fmtDice(te.deg, 3, te.degFlat, te.degBonus)}
-
Régénération${fmtDice(te.reg, 3, te.regFlat)}
+
Attaque${fmtStatLine(t.att, te.att, 6, te.attFlat)}
+
Esquive${fmtStatLine(t.esq, te.esq, 6, te.esqFlat)}
+
Dégâts${fmtStatLine(t.deg, te.deg, 3, te.degFlat, te.degBonus)}
+
Régénération${fmtStatLine(t.reg, te.reg, 3, te.regFlat)}
Armure${te.armor}${t.armorDice ? "+" + t.armorDice + "D3" : ""}
Vue${te.vue}
${RACES[t.race].comp.name}${t.comp.pct} %
diff --git a/js/potions.js b/js/potions.js index 58fba73..1bbaaf3 100644 --- a/js/potions.js +++ b/js/potions.js @@ -72,9 +72,10 @@ const POTION_DEFS = { duration: 5, rollPower: () => potRand(3, 7), build(_t, p) { + const lines = [`DEG +${p} dés`, `REG +${p} dés`]; return { - log: `Elixir de Bonne Bouffe : DEG +${p}, REG +${p} pendant 5 tours.`, - effects: [makeEffect("Elixir de Bonne Bouffe", "🍖", 5, { deg: p, reg: p })], + log: `Elixir de Bonne Bouffe : ${lines.join(", ")} pendant 5 tours.`, + effects: [makeEffect("Elixir de Bonne Bouffe", "🍖", 5, { deg: p, reg: p }, lines)], }; }, }, @@ -153,9 +154,10 @@ const POTION_DEFS = { build(troll, p, rollDice) { const heal = rollDice(2, 3).total; troll.pv = Math.min(troll.pvMax, troll.pv + heal); + const lines = [`DEG +${p} dés`, `REG +${p} dés`, `PV +${heal} (2D3)`]; return { - log: `Extrait de DjhinTonik : DEG +${p}, REG +${p}, +${heal} PV (2D3) pendant 4 tours.`, - effects: [makeEffect("Extrait de DjhinTonik", "🧞", 4, { deg: p, reg: p })], + log: `Extrait de DjhinTonik : ${lines.join(", ")} pendant 4 tours.`, + effects: [makeEffect("Extrait de DjhinTonik", "🧞", 4, { deg: p, reg: p }, lines)], }; }, }, @@ -383,13 +385,27 @@ function sumPotionMods(effects) { mmPct: 0, rmPct: 0, concentrationPct: 0, dlaBonusPA: 0, }; for (const e of effects || []) { + if (e.blockCamo) continue; for (const k of Object.keys(m)) { - if (e[k]) m[k] += e[k]; + if (typeof e[k] === "number") m[k] += e[k]; } } return m; } +/* Affichage caractéristique : valeur effective + rappel de la base si modifiée. */ +function fmtStatLine(base, eff, faces, flat = 0, extra = 0) { + const diceDelta = eff - base; + const jetBonus = (flat || 0) + (extra || 0); + let main = `${eff}D${faces}`; + if (jetBonus) main += jetBonus > 0 ? ` +${jetBonus}` : ` ${jetBonus}`; + if (diceDelta !== 0) { + const sign = diceDelta > 0 ? "+" : ""; + return `${main} (${base}D${faces} ${sign}${diceDelta})`; + } + return main; +} + function effTroll(troll) { const m = sumPotionMods(troll.potionEffects); return { @@ -477,21 +493,20 @@ function tickPotionTurns(troll, logFn) { } const EFFECT_MOD_LABELS = { - att: ["ATT", "dés"], esq: ["ESQ", "dés"], deg: ["DEG", "dés"], reg: ["REG", "dés"], - vue: ["VUE", ""], armor: ["Armure", ""], + att: "ATT", esq: "ESQ", deg: "DEG", reg: "REG", vue: "VUE", armor: "Armure", }; function formatEffectMods(effect) { if (effect.modLines?.length) return [...effect.modLines]; const parts = []; - if (effect.attFlat) parts.push(`ATT ${effect.attFlat > 0 ? "+" : ""}${effect.attFlat}`); - if (effect.esqFlat) parts.push(`ESQ ${effect.esqFlat > 0 ? "+" : ""}${effect.esqFlat}`); - if (effect.degFlat) parts.push(`DEG ${effect.degFlat > 0 ? "+" : ""}${effect.degFlat}`); - if (effect.regFlat) parts.push(`REG ${effect.regFlat > 0 ? "+" : ""}${effect.regFlat}`); - for (const [key, [label, suffix]] of Object.entries(EFFECT_MOD_LABELS)) { + if (effect.attFlat) parts.push(`ATT ${effect.attFlat > 0 ? "+" : ""}${effect.attFlat} (jet)`); + if (effect.esqFlat) parts.push(`ESQ ${effect.esqFlat > 0 ? "+" : ""}${effect.esqFlat} (jet)`); + if (effect.degFlat) parts.push(`DEG ${effect.degFlat > 0 ? "+" : ""}${effect.degFlat} (jet)`); + if (effect.regFlat) parts.push(`REG ${effect.regFlat > 0 ? "+" : ""}${effect.regFlat} (jet)`); + for (const [key, label] of Object.entries(EFFECT_MOD_LABELS)) { const v = effect[key]; - if (!v) continue; - parts.push(`${label} ${v > 0 ? "+" : ""}${v}${suffix ? " " + suffix : ""}`); + if (typeof v !== "number" || !v) continue; + parts.push(`${label} ${v > 0 ? "+" : ""}${v}${["att", "esq", "deg", "reg"].includes(key) ? " dés" : ""}`); } if (effect.mmPct) parts.push(`MM ${effect.mmPct > 0 ? "+" : ""}${effect.mmPct} %`); if (effect.rmPct) parts.push(`RM ${effect.rmPct > 0 ? "+" : ""}${effect.rmPct} %`); @@ -547,14 +562,14 @@ function renderEffectsPanel(troll) { const total = sumPotionMods(troll.potionEffects); const totalLines = []; - if (total.attFlat) totalLines.push(`ATT ${total.attFlat > 0 ? "+" : ""}${total.attFlat}`); - if (total.esqFlat) totalLines.push(`ESQ ${total.esqFlat > 0 ? "+" : ""}${total.esqFlat}`); - if (total.degFlat) totalLines.push(`DEG ${total.degFlat > 0 ? "+" : ""}${total.degFlat}`); - if (total.regFlat) totalLines.push(`REG ${total.regFlat > 0 ? "+" : ""}${total.regFlat}`); - for (const [key, [label, suffix]] of Object.entries(EFFECT_MOD_LABELS)) { + if (total.attFlat) totalLines.push(`ATT ${total.attFlat > 0 ? "+" : ""}${total.attFlat} (jet)`); + if (total.esqFlat) totalLines.push(`ESQ ${total.esqFlat > 0 ? "+" : ""}${total.esqFlat} (jet)`); + if (total.degFlat) totalLines.push(`DEG ${total.degFlat > 0 ? "+" : ""}${total.degFlat} (jet)`); + if (total.regFlat) totalLines.push(`REG ${total.regFlat > 0 ? "+" : ""}${total.regFlat} (jet)`); + for (const [key, label] of Object.entries(EFFECT_MOD_LABELS)) { const v = total[key]; - if (!v) continue; - totalLines.push(`${label} ${v > 0 ? "+" : ""}${v}${suffix ? " " + suffix : ""}`); + if (typeof v !== "number" || !v) continue; + totalLines.push(`${label} ${v > 0 ? "+" : ""}${v}${["att", "esq", "deg", "reg"].includes(key) ? " dés" : ""}`); } if (total.mmPct) totalLines.push(`MM ${total.mmPct > 0 ? "+" : ""}${total.mmPct} %`); if (total.rmPct) totalLines.push(`RM ${total.rmPct > 0 ? "+" : ""}${total.rmPct} %`); @@ -591,6 +606,6 @@ if (typeof module !== "undefined" && module.exports) { POTION_IDS, POTION_DEFS, sumPotionMods, effTroll, formatPotionItem, makeRandomPotion, makePotionItem, drinkPotion, tickPotionTurns, describeActiveEffects, renderEffectsPanel, countActiveEffects, formatEffectMods, - rollMod, talentPctWithPotions, corruptionYZ, + rollMod, fmtStatLine, talentPctWithPotions, corruptionYZ, }; } diff --git a/test/smoke.js b/test/smoke.js index af265cd..85a52d9 100644 --- a/test/smoke.js +++ b/test/smoke.js @@ -185,6 +185,13 @@ assert.strictEqual(p.corruptionYZ(3).y, 0); assert.strictEqual(p.effTroll(troll).deg, 3); assert.strictEqual(p.effTroll(troll).attFlat, 0); } +// DjhinTonik : +X dés de DEG et REG (pas un jet D3) +{ + const t = { att: 3, esq: 3, deg: 4, reg: 1, vue: 3, armor: 0, armorDice: 0, degBonus: 0, pvMax: 30, pv: 20, potionEffects: [], blockCamoTurns: 0, tour: 1 }; + p.drinkPotion(t, p.makePotionItem("djhinTonik", 5), g.rollDice, () => {}); + assert.strictEqual(p.effTroll(t).deg, 9, "DEG 4 + 5 dés potion = 9D3"); + assert.strictEqual(p.effTroll(t).reg, 6, "REG 1 + 5 dés potion = 6D3"); +} // Jet d'attaque : bonus potion ajouté au total des D6 { const r = g.resolveAttack({ att: 3, attFlat: 12, deg: 2, degBonus: 0 }, { esq: 1 });