feat: système d'expérience et transferts de dés d'expérience
Release Creation / build (release) Failing after 1m20s

- phase d'expérience (récompenses collectives et individuelles, objectifs, traumatisme)
- jets d'apprentissage (compétences, spécialités, caractéristiques, réserves, évolution)
- bonus de réserve acquis par apprentissage intégré au max dérivé
- transferts de dés individuels <-> groupe dans le dialogue de jet
- onglet expérience sur la fiche personnage
- guide utilisateur combat (docs/user/COMBAT.md)
This commit is contained in:
2026-08-04 14:14:09 +02:00
parent f2c8cfe246
commit 9cf7d9ac68
12 changed files with 1277 additions and 19 deletions
+146
View File
@@ -0,0 +1,146 @@
/**
* Règles d'apprentissage (phase d'Expérience).
* Helpers purs de difficultés, handicaps et calculs de jets d'Expérience.
*/
export class VermineExperience {
/**
* Difficulté d'un jet d'apprentissage de Compétence selon le niveau visé.
* @param {number} targetLevel niveau visé (1-5)
* @returns {number}
*/
static skillLearningDifficulty(targetLevel) {
switch (parseInt(targetLevel, 10)) {
case 1: return 5 // Débutant
case 2: return 7 // Confirmé
case 3: return 9 // Expert
case 4: return 10 // Maître
case 5: return 10 // Légende
default: return 7
}
}
/**
* Handicap d'un jet d'apprentissage de Compétence.
* Rareté (1-3) + handicap (I) supplémentaire pour le niveau Légende.
* @param {number} targetLevel niveau visé (1-5)
* @param {number} rarity rareté de la compétence (0-3)
* @returns {number}
*/
static skillLearningHandicap(targetLevel, rarity) {
let handicap = parseInt(rarity, 10) || 0
if (parseInt(targetLevel, 10) === 5) handicap += 1 // Légende : (I)
return handicap
}
/**
* Difficulté d'un jet d'apprentissage de Spécialité selon le niveau actuel
* de la compétence associée. Domaine de prédilection : -2.
* @param {number} skillLevel niveau actuel de la compétence (2-5)
* @param {boolean} isDomain la compétence est dans le domaine de prédilection
* @returns {number}
*/
static specialtyLearningDifficulty(skillLevel, isDomain) {
let difficulty
switch (parseInt(skillLevel, 10)) {
case 2: difficulty = 9 // Confirmé
break
case 3: difficulty = 7 // Expert
break
case 4: difficulty = 5 // Maître
break
case 5: difficulty = 5 // Légende (relance automatique)
break
default: difficulty = 9
}
return isDomain ? difficulty - 2 : difficulty
}
/**
* Handicap d'un jet d'apprentissage de Spécialité : nombre de spécialités
* déjà possédées (requiert au moins Confirmé dans la compétence associée).
* @param {number} specialtyCount
* @returns {number}
*/
static specialtyLearningHandicap(specialtyCount) {
return Math.max(0, parseInt(specialtyCount, 10) || 0)
}
/**
* Difficulté d'un jet d'apprentissage de Caractéristique.
* @returns {number}
*/
static characteristicLearningDifficulty() {
return 9
}
/**
* Difficulté d'un jet d'apprentissage de Réserve : valeur actuelle.
* @param {number} currentValue
* @returns {number}
*/
static reserveLearningDifficulty(currentValue) {
return Math.max(0, parseInt(currentValue, 10) || 0)
}
/**
* Handicap d'âge pour l'apprentissage des Réserves et autres jets liés à l'âge.
* @param {number} ageType 1=Jeune, 2=Adulte, 3=Ancien
* @returns {number}
*/
static ageHandicap(ageType) {
switch (parseInt(ageType, 10)) {
case 1: return 1 // Jeune (I)
case 3: return 3 // Ancien (III)
default: return 2 // Adulte (II)
}
}
/**
* Difficulté d'un jet d'acquisition d'un Dé d'Évolution selon l'âge.
* @param {number} ageType 1=Jeune, 2=Adulte, 3=Ancien
* @returns {number}
*/
static evolutionDifficulty(ageType) {
switch (parseInt(ageType, 10)) {
case 1: return 5
case 3: return 9
default: return 7
}
}
/**
* Bonus/Malus de Totem sur le jet de Dé d'Évolution.
* Plus de Dés Adaptés que Humains : +1D. Inversement : -1D.
* @param {number} human human totem dice
* @param {number} adapted adapted totem dice
* @returns {number}
*/
static evolutionTotemModifier(human, adapted) {
if (adapted > human) return 1
if (human > adapted) return -1
return 0
}
/**
* Clé i18n du libellé du niveau de compétence visé.
* @param {number} level
* @returns {string}
*/
static skillLevelLabelKey(level) {
return CONFIG.VERMINE.SkillLevels?.[level]?.label ?? "SKILL_LEVELS.incompetent"
}
/**
* Détermine si une compétence appartient au domaine de prédilection.
* @param {Actor} actor
* @param {string} skillKey
* @returns {boolean}
*/
static isPreferredDomain(actor, skillKey) {
const category = actor.system?.skills?.[skillKey]?.category
const preferred = actor.system?.skill_categories?.preferred
return Boolean(category && preferred && category === preferred)
}
}