feat(experience): atout Socle de compétences — +1D gratuit dans le dialogue d'apprentissage

This commit is contained in:
2026-08-09 07:47:56 +02:00
parent 0ef0de45ff
commit 9ab2c6b01a
4 changed files with 40 additions and 1 deletions
+2
View File
@@ -517,6 +517,8 @@
"characteristic_hint": "Difficulty 9 + Handicap = current value. Max 3 (except Superhuman).",
"reserve_hint": "Difficulty = current value + age Handicap. Max 10.",
"evolution_hint": "Difficulty by age + Handicap = accumulated Evolution dice. Adapted totem +1D, Human -1D.",
"socle_competences_hint": "Socle de compétences atout: +{level} free Experience die (skill already possessed).",
"socle_competences_message": "Socle de compétences: +{level} free Experience die. Reminder: a Group companion who learns or develops with him a skill he possesses also gains 1 Experience die.",
"totem_ability_hint": "Prerequisites: same Totem as the character, Group level ≥ Capacity level, game mode compatible (Survival N1, Nightmare N2, Apocalypse N3).",
"background_hint": "Difficulty 9. Handicap (I) if the Background is marked with an asterisk (*). GM authorization required.",
"reputation_hint": "Reduce = more famous (Difficulty 10 target score, 1 pt max per phase). Increase = fade into obscurity (requires a major Objective, Difficulty = current Reputation, +1 per Success).",
+2
View File
@@ -500,6 +500,8 @@
"characteristic_hint": "Difficulté 9 + Handicap = valeur actuelle. Max 3 (Surhumain excepté).",
"reserve_hint": "Difficulté = valeur actuelle + Handicap d'âge. Max 10.",
"evolution_hint": "Difficulté selon l'âge + Handicap = Dés d'Évolution accumulés. Totem Adapté +1D, Humain -1D.",
"socle_competences_hint": "Atout Socle de compétences : +{level}D d'Expérience gratuit (compétence déjà possédée).",
"socle_competences_message": "Socle de compétences : +{level}D d'Expérience gratuit. Rappel : un camarade du Groupe qui apprend ou développe avec lui une Compétence qu'il possède gagne aussi 1D d'Expérience.",
"totem_ability_hint": "Prérequis : même Totem que le personnage, Niveau de Groupe ≥ Niveau de la Capacité, Mode de jeu compatible (Survie N1, Cauchemar N2, Apocalypse N3).",
"background_hint": "Difficulté 9. Handicap (I) si l'Historique est marqué d'un astérisque (*). Autorisation du MJ requise.",
"reputation_hint": "Réduire = plus célèbre (Difficulté 10 score visé, 1 pt max/phase). Augmenter = se faire oublier (exige un Objectif majeur, Difficulté = Réputation actuelle, +1 par Réussite).",
+35 -1
View File
@@ -145,6 +145,20 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
return { total: items.length, byLevel };
}
/**
* Bonus de l'atout "Socle de compétences" (règles p. 112).
* Rend le niveau (1-3) de l'atout possédé par le personnage, ou 0.
* @returns {number}
*/
#socleCompetencesBonus() {
const item = (this.#actor.itemTypes?.ability || []).find(i =>
i.system?.type === "character"
&& (i.name || "").trim().toLowerCase() === "socle de compétences".toLowerCase()
);
if (!item) return 0;
return Math.min(3, Math.max(1, parseInt(item.system?.level?.value, 10) || 1));
}
async _onRender() {
this.element.dataset.actorId = this.#actor.id;
for (const inp of this.element.querySelectorAll("[data-roll]")) {
@@ -201,13 +215,16 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
label = game.i18n.localize(VermineExperience.skillLevelLabelKey(targetLevel));
const diff = VermineExperience.skillLearningDifficulty(targetLevel);
const domain = key ? VermineExperience.isPreferredDomain(actor, key) : false;
// Atout "Socle de compétences" : 1D gratuit si la Compétence est déjà possédée.
const socleBonus = current >= 1 ? this.#socleCompetencesBonus() : 0;
return {
type,
key,
targetLevel,
difficulty: domain ? Math.max(3, diff - 2) : diff,
handicap: VermineExperience.skillLearningHandicap(targetLevel, rarity),
poolModifier: 0,
poolModifier: socleBonus,
socleBonus,
label,
targetLabel: key ? game.i18n.localize("SKILLS." + key + ".name") : ""
};
@@ -363,6 +380,15 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
set("#diff-value", c.difficulty);
set("#handicap-value", c.handicap);
set("#required-value", 1 + c.handicap);
const socleHint = this.#el.querySelector("#socle-bonus-hint");
if (socleHint) {
if (type === "skill" && c.socleBonus > 0) {
socleHint.style.display = "";
socleHint.textContent = game.i18n.format("VERMINE.socle_competences_hint", { level: c.socleBonus });
} else {
socleHint.style.display = "none";
}
}
const pool = Math.max(0, this.#getDiceSpent() + c.poolModifier);
let poolText = `${pool}D${c.poolModifier ? ` (${c.poolModifier >= 0 ? "+" : ""}${c.poolModifier}D)` : ""}`;
if (c.adaptedDie) poolText += " — " + game.i18n.localize("VERMINE.adapted_die_x2");
@@ -496,6 +522,14 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
if ((roll._total ?? 0) >= required) {
await this.#applySuccess(c, specialtyName, roll._total ?? 0);
}
// Atout "Socle de compétences" : rappeler le bonus conféré aux camarades
// du Groupe qui apprennent une Compétence possédée par le personnage.
if (c.socleBonus > 0) {
await ChatMessage.create({
content: game.i18n.format("VERMINE.socle_competences_message", { level: c.socleBonus }),
speaker: ChatMessage.getSpeaker({ actor })
});
}
// Les Dés d'Expérience sont perdus que le jet soit réussi ou raté.
await actor.update({ "system.experience.dice": Math.max(0, (actor.system.experience.dice || 0) - spent) });
this.close();
+1
View File
@@ -26,6 +26,7 @@
</option>
{{/each}}
</select>
<p class="learning-hint" id="socle-bonus-hint" style="display:none;"></p>
</div>
<div id="learning-specialty-section" class="learning-targets" style="display:none;">