Files
vermine2047/module/system/experience.mjs
T
uberwald c96e8d69d7 fix: retours review - validation de phase, drop de véhicule et dés de Réserve
- resetPhaseLimits ne touche plus au compteur de phase (seul le
  dialogue d'attribution du MJ l'avance) : évite le double-incrément
  quand joueur et MJ valident la même phase.
- _onDropActor : import d'une copie monde pour un véhicule déposé
  depuis un compendium, confirmation avant de transférer un véhicule
  déjà rattaché à un autre Groupe, try/catch avec retour utilisateur.
- #onClickRadioHexa : garde isEditable (pas d'erreur de permission
  pour un utilisateur non éditeur).
2026-08-05 21:24:33 +02:00

237 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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
}
/**
* Un Dé de Totem Adapté peut remplacer un Dé d'Expérience (compte ×2)
* uniquement si le personnage possède strictement plus de Dés Adaptés
* que Humains.
* @param {number} human human totem dice
* @param {number} adapted adapted totem dice
* @returns {boolean}
*/
static canUseAdaptedDie(human, adapted) {
return adapted > human
}
/**
* Difficulté d'un jet d'apprentissage de Capacité de Totem : valeur
* d'Apprentissage de la capacité.
* @param {object} abilityData system data de l'item ability
* @returns {number}
*/
static totemAbilityLearningDifficulty(abilityData) {
return parseInt(abilityData?.learn?.threshold, 10) || 5
}
/**
* Handicap d'un jet d'apprentissage de Capacité de Totem : valeur
* d'Apprentissage de la capacité.
* @param {object} abilityData system data de l'item ability
* @returns {number}
*/
static totemAbilityLearningHandicap(abilityData) {
return parseInt(abilityData?.learn?.hindrance, 10) || 0
}
/**
* Difficulté d'un jet d'obtention d'un Élément d'Historique.
* @returns {number}
*/
static backgroundLearningDifficulty() {
return 9
}
/**
* Handicap d'un jet d'obtention d'un Élément d'Historique : (I) si
* l'Historique est listé avec un astérisque.
* @param {boolean} asterisk
* @returns {number}
*/
static backgroundLearningHandicap(asterisk) {
return asterisk ? 1 : 0
}
/**
* Difficulté d'un jet visant à réduire le score de Réputation (devenir
* plus célèbre) : 10 moins le score que le personnage souhaite atteindre.
* @param {number} current réputation actuelle
* @returns {number}
*/
static reputationReduceDifficulty(current) {
return 10 - Math.max(0, (parseInt(current, 10) || 0) - 1)
}
/**
* Difficulté d'un jet visant à augmenter le score de Réputation (se faire
* oublier) : égale à la Réputation actuelle.
* @param {number} current réputation actuelle
* @returns {number}
*/
static reputationIncreaseDifficulty(current) {
return parseInt(current, 10) || 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)
}
/**
* Réinitialise les limites d'apprentissage (learned.*) de la phase
* courante, pour un personnage ou un Groupe, sans toucher au compteur
* de phase (avancé uniquement par le dialogue d'attribution du MJ).
* @param {foundry.abstract.Document} actor personnage ou Groupe
* @returns {Promise<void>}
*/
static async resetPhaseLimits(actor) {
const ex = actor.system?.experience
if (!ex) return
const update = {}
if (ex.learned && typeof ex.learned === "object") {
for (const key of Object.keys(ex.learned)) {
if (key === "phase") continue
update[`system.experience.learned.${key}`] = false
}
}
if (Object.keys(update).length) await actor.update(update)
}
}