Introduction
Le systeme de des est au coeur de tout JDR. FoundryVTT fournit une classe Roll puissante et extensible pour parser des formules, lancer les des et afficher les resultats.
Roll (Foundry Core)
|
+-- BasicRoll (Systeme)
| |
| +-- D20Roll (Jets de d20)
| |
| +-- DamageRoll (Jets de degats)
|
+-- ChatMessage (Affichage)
La classe Roll
La classe Roll est la base de tout jet de des dans FoundryVTT.
Creation d'un jet simple
// Jet de base
const roll = new Roll("1d20 + 5");
// Avec des donnees dynamiques
const roll = new Roll("1d20 + @abilities.str.mod", {
abilities: { str: { mod: 3 } }
});
// Avec des options
const roll = new Roll("1d20 + @mod", data, {
flavor: "Jet d'attaque",
rollMode: "gmroll"
});
Proprietes importantes
const roll = new Roll("2d6 + 3");
await roll.evaluate();
// Resultat total
console.log(roll.total); // ex: 11
// Formule originale
console.log(roll.formula); // "2d6 + 3"
// Resultat detaille
console.log(roll.result); // ex: "5 + 3 + 3"
// Termes du jet (des, operateurs, nombres)
console.log(roll.terms); // [Die, OperatorTerm, NumericTerm]
// Uniquement les des
console.log(roll.dice); // [Die(2d6)]
// Le jet a-t-il ete evalue ?
console.log(roll._evaluated); // true
Formules et modificateurs
Syntaxe de base
| Formule | Description |
|---|---|
1d20 | Lance 1 de a 20 faces |
2d6 | Lance 2 des a 6 faces |
d20 | Raccourci pour 1d20 |
4d6 + 2 | 4d6 plus un modificateur fixe |
1d20 + 1d4 | Combinaison de des |
(2d6 + 3) * 2 | Operations mathematiques |
Modificateurs de des
| Modificateur | Description | Exemple |
|---|---|---|
kh / kh1 |
Keep Highest (garder le plus haut) | 2d20kh1 - Avantage |
kl / kl1 |
Keep Lowest (garder le plus bas) | 2d20kl1 - Desavantage |
dh / dh1 |
Drop Highest (retirer le plus haut) | 4d6dh1 |
dl / dl1 |
Drop Lowest (retirer le plus bas) | 4d6dl1 - Stats D&D |
r<2 |
Reroll (relancer si condition) | 1d20r<2 |
ro=1 |
Reroll Once (relancer une fois) | 1d20ro=1 - Halfelin |
x>=6 |
Exploding (exploser si condition) | 1d6x>=6 |
min3 |
Minimum par de | 1d20min10 - Reliable Talent |
cs>=8 |
Count Successes | 10d10cs>=8 |
// Exemples pratiques
const avantage = new Roll("2d20kh1 + @mod", { mod: 5 });
const desavantage = new Roll("2d20kl1 + @mod", { mod: 5 });
const stats = new Roll("4d6dl1"); // Generation de caracteristique
const halflingLucky = new Roll("1d20ro=1 + @mod", { mod: 3 });
const explosion = new Roll("4d6x>=6"); // Des qui explosent
Variables (@data)
Les formules peuvent referencer des variables via la syntaxe @nomVariable.
// Donnees de l'acteur
const rollData = {
abilities: {
str: { value: 16, mod: 3 },
dex: { value: 14, mod: 2 }
},
prof: 3,
level: 5
};
// Reference simple
const roll1 = new Roll("1d20 + @prof", rollData);
// Formule: "1d20 + 3"
// Reference imbriquee
const roll2 = new Roll("1d20 + @abilities.str.mod", rollData);
// Formule: "1d20 + 3"
// Calcul avec variable
const roll3 = new Roll("@level * d6", rollData);
// Formule: "5d6"
// Valeur par defaut si undefined
const roll4 = new Roll("1d20 + (@bonus || 0)", { bonus: undefined });
// Formule: "1d20 + 0"
getRollData() de l'acteur
La methode getRollData() fournit toutes les donnees utiles pour les jets :
// Dans la classe Actor
getRollData() {
const data = { ...super.getRollData() };
// Ajouter les modificateurs de caracteristiques
for (const [key, ability] of Object.entries(this.system.abilities)) {
data.abilities[key] = {
...ability,
mod: Math.floor((ability.value - 10) / 2)
};
}
// Bonus de maitrise
data.prof = this.system.attributes.prof;
// Niveau
data.level = this.system.details.level;
return data;
}
// Utilisation
const actor = game.actors.getName("Mon Personnage");
const roll = new Roll("1d20 + @abilities.str.mod + @prof", actor.getRollData());
Evaluation des jets
Evaluation asynchrone (recommandee)
const roll = new Roll("2d6 + 3");
// Evaluation asynchrone
await roll.evaluate();
console.log(roll.total); // Resultat disponible
Evaluation synchrone (v13+)
const roll = new Roll("1d20 + 5");
// Evaluation synchrone (moins recommandee)
roll.evaluateSync();
console.log(roll.total);
Options d'evaluation
// Maximiser tous les des
const maxRoll = new Roll("4d6");
await maxRoll.evaluate({ maximize: true });
console.log(maxRoll.total); // 24 (6+6+6+6)
// Minimiser tous les des
const minRoll = new Roll("4d6");
await minRoll.evaluate({ minimize: true });
console.log(minRoll.total); // 4 (1+1+1+1)
// Valider une formule sans evaluer
const isValid = Roll.validate("2d6 + @mod");
console.log(isValid); // true ou false
Acces aux resultats detailles
const roll = new Roll("2d6 + 1d8 + 5");
await roll.evaluate();
// Parcourir les termes
for (const term of roll.terms) {
if (term instanceof Die) {
console.log(`${term.number}d${term.faces}:`);
for (const result of term.results) {
console.log(` - ${result.result} (active: ${result.active})`);
}
}
}
// Acces direct aux des
const d6 = roll.dice[0]; // Premier groupe de des (2d6)
console.log(d6.total); // Somme des 2d6
console.log(d6.results); // [{result: 4, active: true}, {result: 2, active: true}]
Envoi vers le Chat
Methode toMessage()
const roll = new Roll("1d20 + 5");
await roll.evaluate();
// Envoi simple
await roll.toMessage();
// Avec options
await roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: myActor }),
flavor: "Jet de Dexterite (Acrobaties)",
rollMode: game.settings.get("core", "rollMode")
});
// Avec flags personnalises
await roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor }),
flavor: "Jet d'attaque - Epee longue",
flags: {
"mon-systeme": {
type: "attack",
weaponId: weapon.id,
actorId: actor.id
}
}
});
Roll modes
// Jet public (visible par tous)
await roll.toMessage({ rollMode: "publicroll" });
// Jet prive MJ (visible par le joueur et le MJ)
await roll.toMessage({ rollMode: "gmroll" });
// Jet aveugle (visible uniquement par le MJ)
await roll.toMessage({ rollMode: "blindroll" });
// Jet personnel (visible uniquement par le joueur)
await roll.toMessage({ rollMode: "selfroll" });
// Utiliser le mode par defaut de l'utilisateur
const rollMode = game.settings.get("core", "rollMode");
await roll.toMessage({ rollMode });
Creer le message sans l'envoyer
// Obtenir les donnees du message sans le creer
const messageData = await roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor }),
flavor: "Mon jet"
}, { create: false });
// Modifier les donnees
messageData.content = `${messageData.content}`;
// Creer le message manuellement
await ChatMessage.create(messageData);
Rolls personnalises
Vous pouvez etendre la classe Roll pour creer des comportements specifiques a votre systeme.
// module/dice/basic-roll.mjs
export default class BasicRoll extends Roll {
/**
* Propriete de succes basee sur une cible
*/
get isSuccess() {
if (!this._evaluated) return undefined;
if (!Number.isNumeric(this.options.target)) return undefined;
return this.total >= this.options.target;
}
/**
* Propriete d'echec
*/
get isFailure() {
if (!this._evaluated) return undefined;
if (!Number.isNumeric(this.options.target)) return undefined;
return this.total < this.options.target;
}
/**
* Workflow complet : configuration -> evaluation -> message
*/
static async build(config = {}, dialog = {}, message = {}) {
// 1. Creer le roll depuis la configuration
const formula = (config.parts ?? []).join(" + ");
const roll = new this(formula, config.data, config.options);
// 2. Afficher un dialogue si necessaire
if (dialog.configure) {
const configured = await this.showDialog(roll, dialog);
if (!configured) return null; // Annule
}
// 3. Evaluer le jet
await roll.evaluate();
// 4. Envoyer au chat
if (message.create !== false) {
await roll.toMessage(message.data);
}
return roll;
}
}
// Enregistrement dans CONFIG
Hooks.once("init", () => {
CONFIG.Dice.BasicRoll = BasicRoll;
});
D20Roll : avantage et critiques
La classe D20Roll est specialisee pour les jets de d20 avec gestion de l'avantage/desavantage et des critiques.
// module/dice/d20-roll.mjs
export default class D20Roll extends BasicRoll {
// Modes d'avantage
static ADV_MODE = {
NORMAL: 0,
ADVANTAGE: 1,
DISADVANTAGE: -1
};
constructor(formula, data, options = {}) {
super(formula, data, options);
// Configurer le mode d'avantage
if (!this.options.configured) {
this.configureModifiers();
}
}
/**
* Reference au de d20 principal
*/
get d20() {
return this.dice[0];
}
/**
* Est-ce un critique ?
*/
get isCritical() {
if (!this._evaluated) return undefined;
const threshold = this.options.criticalSuccess ?? 20;
return this.d20.total >= threshold;
}
/**
* Est-ce un echec critique ?
*/
get isFumble() {
if (!this._evaluated) return undefined;
const threshold = this.options.criticalFailure ?? 1;
return this.d20.total <= threshold;
}
/**
* Configure les modificateurs selon les options
*/
configureModifiers() {
const { advantage, disadvantage } = this.options;
// Determiner le mode
if (advantage && !disadvantage) {
this.options.advantageMode = D20Roll.ADV_MODE.ADVANTAGE;
} else if (!advantage && disadvantage) {
this.options.advantageMode = D20Roll.ADV_MODE.DISADVANTAGE;
} else {
this.options.advantageMode = D20Roll.ADV_MODE.NORMAL;
}
// Modifier la formule du d20
const d20 = this.terms[0];
if (d20 instanceof Die && d20.faces === 20) {
// Supprimer les anciens modificateurs kh/kl
d20.modifiers = d20.modifiers.filter(m => !["kh", "kl"].includes(m));
switch (this.options.advantageMode) {
case D20Roll.ADV_MODE.ADVANTAGE:
d20.number = this.options.elvenAccuracy ? 3 : 2;
d20.modifiers.push("kh");
break;
case D20Roll.ADV_MODE.DISADVANTAGE:
d20.number = 2;
d20.modifiers.push("kl");
break;
default:
d20.number = 1;
}
// Halfelin chanceux (relancer les 1)
if (this.options.halflingLucky) {
d20.modifiers.push("ro=1");
}
// Reliable Talent (minimum 10)
if (this.options.reliableTalent) {
d20.modifiers.push("min10");
}
}
// Reconstruire la formule
this._formula = this.constructor.getFormula(this.terms);
this.options.configured = true;
}
}
// Enregistrement
Hooks.once("init", () => {
CONFIG.Dice.D20Roll = D20Roll;
});
Utilisation de D20Roll
// Jet simple
const roll = new D20Roll("1d20 + @mod", { mod: 5 });
await roll.evaluate();
await roll.toMessage({ flavor: "Jet de sauvegarde" });
// Avec avantage
const advRoll = new D20Roll("1d20 + @mod", { mod: 5 }, {
advantage: true
});
await advRoll.evaluate();
// Formule devient: "2d20kh + 5"
// Avec desavantage et Halfelin chanceux
const disadvRoll = new D20Roll("1d20 + @mod", { mod: 3 }, {
disadvantage: true,
halflingLucky: true
});
await disadvRoll.evaluate();
// Formule devient: "2d20klro=1 + 3"
// Verification du critique
if (roll.isCritical) {
console.log("Critique !");
} else if (roll.isFumble) {
console.log("Echec critique !");
}
DamageRoll : degats
La classe DamageRoll gere les jets de degats avec multiplicateurs critiques.
// module/dice/damage-roll.mjs
export default class DamageRoll extends BasicRoll {
constructor(formula, data, options = {}) {
super(formula, data, options);
if (!this.options.configured) {
this.configureDamage();
}
}
/**
* Est-ce un jet critique ?
*/
get isCritical() {
return this.options.isCritical === true;
}
/**
* Type de degats (slashing, fire, etc.)
*/
get damageType() {
return this.options.type;
}
/**
* Configure les degats critiques
*/
configureDamage() {
if (!this.isCritical) {
this.options.configured = true;
return;
}
const critical = this.options.critical ?? {};
const multiplier = critical.multiplier ?? 2;
const bonusDice = critical.bonusDice ?? 0;
// Doubler les des
for (const term of this.terms) {
if (term instanceof Die) {
// Sauvegarder le nombre original
term.options.baseNumber = term.options.baseNumber ?? term.number;
// Appliquer le multiplicateur
term.number = term.options.baseNumber * multiplier;
// Ajouter les des bonus
if (bonusDice > 0) {
term.number += bonusDice;
}
term.options.critical = true;
}
}
// Ajouter des degats bonus (ex: "1d8" de Brutal Critical)
if (critical.bonusDamage) {
const bonusTerms = new Roll(critical.bonusDamage, this.data).terms;
this.terms.push(new OperatorTerm({ operator: "+" }));
this.terms.push(...bonusTerms);
}
// Reconstruire la formule
this._formula = this.constructor.getFormula(this.terms);
this.options.configured = true;
}
}
// Enregistrement
Hooks.once("init", () => {
CONFIG.Dice.DamageRoll = DamageRoll;
});
Utilisation de DamageRoll
// Degats normaux
const damage = new DamageRoll("2d6 + @mod", { mod: 4 }, {
type: "slashing"
});
await damage.evaluate();
await damage.toMessage({ flavor: "Degats - Epee longue" });
// Degats critiques
const critDamage = new DamageRoll("2d6 + @mod", { mod: 4 }, {
type: "slashing",
isCritical: true,
critical: {
multiplier: 2, // Double les des
bonusDice: 1 // +1 de bonus (Brutal Critical)
}
});
await critDamage.evaluate();
// Formule devient: "5d6 + 4" (2*2 + 1 des)
// Plusieurs types de degats
const fireDamage = new DamageRoll("1d6", {}, {
type: "fire",
properties: ["magical"] // Proprietes pour les resistances
});
// Agregation des degats
const totalDamage = damage.total + fireDamage.total;
Exercice pratique : Creer un jet de competence
Implementons une fonction complete de jet de competence.
// module/dice/skill-roll.mjs
/**
* Effectue un jet de competence
* @param {Actor} actor - L'acteur qui fait le jet
* @param {string} skillId - Identifiant de la competence
* @param {object} options - Options du jet
* @returns {Promise}
*/
export async function rollSkill(actor, skillId, options = {}) {
// 1. Recuperer les donnees de la competence
const skill = actor.system.skills[skillId];
if (!skill) {
ui.notifications.error(`Competence "${skillId}" introuvable.`);
return null;
}
// 2. Recuperer les donnees de caracteristique associee
const ability = actor.system.abilities[skill.ability];
const rollData = actor.getRollData();
// 3. Construire les parties de la formule
const parts = ["1d20"];
// Modificateur de caracteristique
parts.push("@abilityMod");
rollData.abilityMod = ability.mod;
// Bonus de maitrise si competent
if (skill.proficient) {
parts.push("@prof");
rollData.prof = actor.system.attributes.prof;
// Expertise (double maitrise)
if (skill.expertise) {
rollData.prof *= 2;
}
}
// Bonus divers
if (skill.bonus) {
parts.push("@skillBonus");
rollData.skillBonus = skill.bonus;
}
// 4. Determiner l'avantage/desavantage
const advantageMode = determineAdvantage(actor, skillId, options);
// 5. Creer le roll
const roll = new CONFIG.Dice.D20Roll(parts.join(" + "), rollData, {
advantage: advantageMode === 1,
disadvantage: advantageMode === -1,
reliableTalent: skill.value >= 2 && actor.flags?.dnd5e?.reliableTalent,
halflingLucky: actor.flags?.dnd5e?.halflingLucky,
target: options.dc // Difficulte optionnelle
});
// 6. Afficher le dialogue de configuration (optionnel)
if (!options.fastForward) {
const configured = await showRollDialog(roll, {
title: `${CONFIG.MON_SYSTEME.skills[skillId].label} (${CONFIG.MON_SYSTEME.abilities[skill.ability].label})`,
defaultRollMode: game.settings.get("core", "rollMode")
});
if (!configured) return null; // Dialogue annule
}
// 7. Evaluer le jet
await roll.evaluate();
// 8. Envoyer au chat
const abilityLabel = CONFIG.MON_SYSTEME.abilities[skill.ability]?.label ?? skill.ability;
const skillLabel = CONFIG.MON_SYSTEME.skills[skillId]?.label ?? skillId;
await roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor }),
flavor: `${skillLabel} (${abilityLabel})`,
rollMode: options.rollMode ?? game.settings.get("core", "rollMode"),
flags: {
"mon-systeme": {
roll: {
type: "skill",
skillId: skillId,
abilityId: skill.ability
}
}
}
});
// 9. Retourner le roll pour traitement ulterieur
return roll;
}
/**
* Determine le mode d'avantage pour un jet
*/
function determineAdvantage(actor, skillId, options) {
// Avantage/desavantage force
if (options.advantage) return 1;
if (options.disadvantage) return -1;
// Verifier les effets actifs sur l'acteur
// (implementation simplifiee)
for (const effect of actor.effects) {
if (effect.disabled) continue;
// Logique de verification des effets...
}
return 0; // Normal
}
/**
* Affiche un dialogue de configuration du jet
*/
async function showRollDialog(roll, options = {}) {
return new Promise((resolve) => {
new Dialog({
title: options.title ?? "Configuration du jet",
content: `
`,
buttons: {
roll: {
icon: '',
label: "Lancer",
callback: (html) => {
const form = html.find("form")[0];
// Appliquer les options du formulaire au roll
roll.options.advantage = form.advantage.checked;
roll.options.disadvantage = form.disadvantage.checked;
roll.configureModifiers();
resolve(true);
}
},
cancel: {
icon: '',
label: "Annuler",
callback: () => resolve(false)
}
},
default: "roll"
}).render(true);
});
}
// Export pour utilisation dans le systeme
export { rollSkill };
Integration dans l'acteur
// Dans la classe Actor
import { rollSkill } from "../dice/skill-roll.mjs";
class MonActor extends Actor {
/**
* Effectue un jet de competence
* @param {string} skillId - Identifiant de la competence
* @param {object} options - Options du jet
*/
async rollSkill(skillId, options = {}) {
return rollSkill(this, skillId, options);
}
}
// Utilisation depuis une sheet ou un script
const actor = game.actors.getName("Mon Personnage");
await actor.rollSkill("acrobatics", { fastForward: true });
Vous maitrisez maintenant le systeme de des de FoundryVTT. Vous pouvez creer des jets personnalises avec avantage, critiques, et les afficher dans le chat avec style.