Nouvelle fenêtre de jets de dés

This commit is contained in:
2025-09-05 01:09:32 +02:00
parent 652c435833
commit 1ff32697f4
134 changed files with 4025 additions and 400 deletions

View File

@@ -0,0 +1,60 @@
import { ActorToken } from "../actor-token.mjs"
export class RollBasicParts {
restore(rollData) {
rollData.ids.sceneId = rollData.ids.sceneId ?? canvas.scene.id
rollData.active = RollBasicParts.$getActor(rollData)
rollData.opponent = RollBasicParts.$getOpponent(rollData)
if (rollData.mode.opposed == undefined) {
rollData.mode.opposed = rollData.opponent != null
}
}
initFrom(rollData) {
return {
selected: {},
mode: {
current: rollData.mode.current
},
ids: {
sceneId: rollData.ids.sceneId,
actorId: rollData.active.id,
actorTokenId: rollData.active.tokenId,
opponentId: rollData.mode.opposed ? rollData.opponent.id : undefined,
opponentTokenId: rollData.mode.opposed ? rollData.opponent.tokenId : undefined,
}
}
}
static $getActor(rollData) {
if (rollData.ids.actorTokenId) {
return ActorToken.fromTokenId(rollData.ids.actorTokenId, rollData.ids.sceneId)
}
else {
const actorId = rollData.ids.actorId ?? (canvas.tokens.controlled.length == 1
/** TODO: jets de plusieurs personnages??? */
? canvas.tokens.controlled[0]
: undefined)
return ActorToken.fromActorId(actorId, () => { throw new Error("Pas d'acteur sélectionné") })
}
}
static $getOpponent(rollData) {
if (rollData.ids.opponentTokenId) {
return ActorToken.fromTokenId(rollData.ids.opponentTokenId, rollData.ids.sceneId)
}
else if (rollData.ids.opponentId) {
return ActorToken.fromActorId(rollData.ids.opponentId)
}
else {
const targets = Array.from(game.user.targets)
if (targets.length == 1) {
return ActorToken.fromToken(targets[0])
}
else {
return undefined
}
}
}
}

View File

@@ -0,0 +1,28 @@
export const ROLL_MODE_ATTAQUE = 'attaque'
export const ROLL_MODE_COMP = 'comp'
export const ROLL_MODE_DEFENSE = 'defense'
export const ROLL_MODE_JEU = 'jeu'
export const ROLL_MODE_MEDITATION = 'meditation'
export const ROLL_MODE_OEUVRE = 'oeuvre'
export const ROLL_MODE_SORT = 'sort'
export const ROLL_MODE_TACHE = 'tache'
export const DIFF_MODE = {
LIBRE: 'libre',
ATTAQUE: 'attaque',
IMPOSEE: 'imposee',
DEFENSE: 'defense',
DEFAUT: 'defaut',
AUCUN: 'aucun'
}
export const DIFF_MODES = {
[DIFF_MODE.LIBRE]: { key: DIFF_MODE.LIBRE, label: "Difficulté libre", libre: true, visible: true, max: 0 },
[DIFF_MODE.ATTAQUE]: { key: DIFF_MODE.ATTAQUE, label: "Difficulté d'attaque", libre: true, visible: true, max: 0 },
[DIFF_MODE.IMPOSEE]: { key: DIFF_MODE.IMPOSEE, label: "Diffficulté imposée", libre: false, visible: true, max: 0 },
[DIFF_MODE.DEFENSE]: { key: DIFF_MODE.DEFENSE, label: "Diffficulté défense", libre: false, visible: true, max: 0 },
[DIFF_MODE.DEFAUT]: { key: DIFF_MODE.DEFAUT, label: "Difficulté", libre: true, visible: true, max: 5 },
[DIFF_MODE.AUCUN]: { key: DIFF_MODE.AUCUN, label: "", libre: false, visible: false, max: 0 },
}

View File

@@ -0,0 +1,90 @@
import { Misc } from "../misc.js";
import { PART_APPELMORAL } from "./roll-part-appelmoral.mjs";
import { PART_COMP } from "./roll-part-comp.mjs";
import { RdDResolutionTable } from "../rdd-resolution-table.js";
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
import { PART_OEUVRE } from "./roll-part-oeuvre.mjs";
/* -------------------------------------------- */
export class RollDialogAdapter {
async rollDice(rollData, rollTitle) {
const chances = this.computeChances({
carac: rollData.current.carac.value,
diff: rollData.current.totaldiff,
bonus: rollData.current.bonus,
sign: rollData.current.sign,
showDice: rollData.options.showDice,
rollMode: rollData.current.rollmode.key
})
const rolled = await this.rollChances(rollData, chances)
this.adjustRollDataForV1(rollData, rolled, rollTitle)
return rolled
}
computeChances({ carac, diff, bonus, sign, showDice, rollMode }) {
const chances = foundry.utils.duplicate(RdDResolutionTable.computeChances(carac, diff))
RdDResolutionTable._updateChancesWithBonus(chances, bonus, diff)
RdDResolutionTable._updateChancesFactor(chances, sign)
chances.showDice = showDice
chances.rollMode = rollMode
return chances
}
async rollChances(rollData, chances) {
const rolled = await RdDResolutionTable.rollChances(chances, rollData.current.sign, rollData.current.resultat)
rolled.caracValue = rollData.current.carac.value
rolled.finalLevel = rollData.current.totaldiff
rolled.bonus = rollData.current.bonus ?? 0
rolled.factorHtml = Misc.getFractionOneN(rollData.current.sign.diviseur)
return rolled
}
adjustRollDataForV1(rollData, rolled, rollTitle) {
// temporaire pour être homogène roll v1
rollData.alias = rollData.active.actor.getAlias()
// pour experience
rollData.finalLevel = rollData.current.totaldiff
if (rollData.use == undefined) { rollData.use = {} }
if (rollData.show == undefined) { rollData.show = {} }
if (rollData.ajustements == undefined) {
rollData.ajustements = {}
}
rollData.selectedCarac = rollData.active.actor.system.carac[rollData.current.carac.key]
const compKey = rollData.current.comp?.key
if (compKey) {
rollData.competence = rollData.refs[PART_COMP].all.find(it => it.key == compKey)?.comp
rollData.jetResistance = rollData.mode.jetResistance
}
const oeuvreKey = rollData.current.oeuvre?.key
if (oeuvreKey) {
const oeuvreCurrent = rollData.current[PART_OEUVRE];
rollData.oeuvre = oeuvreCurrent.oeuvre
// rollData.oeuvre = rollData.refs[PART_OEUVRE].oeuvres.find(it => it.key == oeuvreKey)?.oeuvre
rollData.art = oeuvreCurrent.art.type
}
// pour appel moral
rollData.diviseurSignificative = rollData.current.sign
if (rollData.current[PART_APPELMORAL]?.checked) {
rollData.use.moral = true
}
rollData.rolled = rolled
if (ReglesOptionnelles.isUsing("afficher-colonnes-reussite")) {
rolled.niveauNecessaire = this.findNiveauNecessaire(carac, rolled.roll)
rolled.ajustementNecessaire = rolled.niveauNecessaire - diff
}
rollData.ajustements = rollData.ajustements.map(aj => {
return {
used: true,
label: aj.label,
value: aj.diff,
descr: aj.diff == undefined ? aj.label : undefined
}
})
rollData.show.title = rollTitle
}
}

439
module/roll/roll-dialog.mjs Normal file
View File

@@ -0,0 +1,439 @@
import { Misc } from "../misc.js";
import { RollModeComp } from "./roll-mode-comp.mjs";
import { RollModeTache } from "./roll-mode-tache.mjs";
import { RollModeAttaque } from "./roll-mode-attaque.mjs";
import { RollModeDefense } from "./roll-mode-defense.mjs";
import { RollModeMeditation } from "./roll-mode-meditation.mjs";
import { RollModeSort } from "./roll-mode-sort.mjs";
import { RollModeOeuvre } from "./roll-mode-oeuvre.mjs";
import { RollModeJeu } from "./roll-mode-jeu.mjs";
import { RollPartAction } from "./roll-part-action.mjs";
import { RollPartActor } from "./roll-part-actor.mjs";
import { RollPartAppelMoral } from "./roll-part-appelmoral.mjs";
import { RollPartAstrologique } from "./roll-part-astrologique.mjs";
import { RollPartCarac } from "./roll-part-carac.mjs";
import { RollPartCoeur } from "./roll-part-coeur.mjs";
import { PART_COMP, RollPartComp } from "./roll-part-comp.mjs";
import { RollPartConditions } from "./roll-part-conditions.mjs";
import { RollPartDiff } from "./roll-part-diff.mjs";
import { RollPartEncTotal } from "./roll-part-enctotal.mjs";
import { RollPartEtat } from "./roll-part-etat.mjs";
import { RollPartEthylisme } from "./roll-part-ethylisme.mjs";
import { RollPartMalusArmure } from "./roll-part-malusarmure.mjs";
import { RollPartMeditation } from "./roll-part-meditation.mjs";
import { RollPartMoral } from "./roll-part-moral.mjs";
import { RollPartOpponent } from "./roll-part-opponent.mjs";
import { RollPartSurEnc } from "./roll-part-surenc.mjs";
import { RollPartTricher } from "./roll-part-tricher.mjs";
import { RollPartTache } from "./roll-part-tache.mjs";
import { RollPartOeuvre } from "./roll-part-oeuvre.mjs";
import { RollPartSort } from "./roll-part-sort.mjs";
import { RollBasicParts } from "./roll-basic-parts.mjs";
import { RollPartRollMode } from "./roll-part-rollmode.mjs";
import { RollPartJeu } from "./roll-part-jeu.mjs";
import { RollPartSign } from "./roll-part-sign.mjs";
import { RollPartAttaque } from "./roll-part-attaque.mjs";
import { RollPartDefense } from "./roll-part-defense.mjs";
import { RollDialogAdapter } from "./roll-dialog-adapter.mjs";
import { ROLLDIALOG_SECTION } from "./roll-part.mjs";
import { ROLL_MODE_COMP } from "./roll-constants.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api
const doNothing = (dialog) => { }
const ROLL_MODE_TABS = [
new RollModeComp(),
new RollModeTache(),
new RollModeAttaque(),
new RollModeDefense(),
// new RollModeParade??
// new RollModeEsquive??
// new RollModeResistance ??
new RollModeSort(),
new RollModeMeditation(),
new RollModeOeuvre(),
new RollModeJeu(),
]
const BASIC_PARTS = new RollBasicParts()
const ROLL_PARTS = [
new RollPartActor(),
new RollPartAction(),
new RollPartOpponent(),
new RollPartCarac(),
new RollPartComp(),
new RollPartDiff(),
new RollPartAttaque(),
new RollPartDefense(),
new RollPartMeditation(),
new RollPartSort(),
new RollPartTache(),
new RollPartOeuvre(),
new RollPartJeu(),
new RollPartSign(),
new RollPartEtat(),
new RollPartConditions(),
new RollPartEthylisme(),
new RollPartMalusArmure(),
new RollPartEncTotal(),
new RollPartSurEnc(),
new RollPartAppelMoral(),
new RollPartMoral(),
new RollPartCoeur(),
new RollPartAstrologique(),
new RollPartTricher(),
new RollPartRollMode(),
]
/**
* Extend the base Dialog entity to select roll parameters
* @extends {Dialog}
* # Principes
* - une seule fenêtre de dialogue (classe RollDialog)
* - plusieurs modes de fonctionnement (classe RollMode)
* - gestion uniforme des modificateurs (classe RollPart)
* - un objet rollData contient les informations liées à un jet de dés
* - un rollData doit pouvoir être "réduit" pour fournir les informations significatives
* d'un jet de dés
* - un rollData réduit doit pouvoir être complété pour afficher la même fenêtre
* - un rollData réduit sera utilisé pour piloter l'ouverture de la fenêtre
*
* - TODO: une classe de base RollChatMessage gerera les messages correspondant aux résultats du dés
* - TODO: réfléchir aux messages supplémentaires gérés par RdDCombat ?
*
* ## Modes de fonctionnement - RollMode
*
* Un mode de fonctionnement (RollMode) détermine quelles parties (RollPart) de la
* fenêtre RollDialog sont actives, mais aussi quels sont les effets du jet.
*
* - chaque mode de fonctionnement peut impacter les RollPart utilisés, les données
* attendues et ajoutées au rollData.
* - chaque mode de fonctionnement peut définir le template de ChatMessage correspondant
* - Le mode de fonctionnement détermine aussi quelles sont les effets du jet:
* - quelle ChatMessage afficher dans le tchat?
* - en cas d'attaque/de défense, quelles sont les suites à donner?
* - en cas de lancement de sort, réduire les points de rêve
* - en cas de méditation, créer le signe draconique
* - en cas de tâche, ajuster les points de tâche
*
*
* ## Modificateurs - RollPart
* - Chaque modificateur a:
* - un code (comp, carac, diff, ...)
* - une partie dédiée pour sauvegarder son contexte
* - le contexte d'un RollPart est stocké dans le rollData de la fenêtre RollDialog,
* dans des parties dédiés:
* - `rollData.refs[code]` pour les données de référentiel (liste de compétences, ...)
* - `rollData.current[code]` pour les informations d'état courante (la compétence sélectionnée, ...)
* - `rollData.selected[code]` pour les informations à sauvegarder, et utilisées pour paramétrer l'ouverture
* - Chaque RollPart gère ses données dans cet espace dédié.
* - Chaque RollPart a un sous-template dédié, et indique où il doit s'afficher dans le RollDialog
* - Chaque RollPart peut enregistrer ses propres events handlers pour mettre à jour son contexte (et généralement réafficher le RollDialo)
* - Chaque RollPart fournit les informations contextuelles associées au jet
* - TODO: chaque RollPart peut fournir un sous-template pour le ChatMessage correspondant au résultat du dé.
*
* ## boucle de rétroaction
* Lors de l'affichage, chaque RollPart peut fournir un filtre pour les autres RollParts.
* Ce filtre sert principalement à filtrer les caractéristiques/compétense.
*
* Une fois ce filtrage effectué, chaque RollPart va pouvoir modifier sa partie du contexte
* de la fenêtre, permettant à son template hbs d'avoir les donnéers à afficher.
*
* Enfin, lors de l'affichage (vu que les contrêles sont réaffichés), il peut
* enregistrer les listeners appropriés.
*
* ## Utilisation des informations sélectionnées
*
* Le rollData est la structure de stockage, et sert à préparer le jet de dé.
* Le résultat du jet est stocké dans le noeud `rollData.rolled` (comme pour
* la première version de jets de dés)
*
*
* # TODO
* - intégration pour un jet (oeuvres / tâches / méditation / compétence)
* - RdDRollResult V2 (affichage avec templates basés sur roll-dialog)
* - Extraction de jet résumé (pour appel chance)
* - gestion significative
* - Attaque
* - Défense
* - intégration rdd-combat
* - combat rencontres
*
*/
/* -------------------------------------------- */
export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2)
{
static init() {
}
static onReady() {
foundry.applications.handlebars.loadTemplates({
'roll-section': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-section.hbs',
'roll-mode': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-mode.hbs',
'roll-table': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-table.hbs',
'roll-ajustements': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-ajustements.hbs',
'roll-chances': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-chances.hbs',
'roll-button': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-button.hbs',
})
foundry.applications.handlebars.loadTemplates(ROLL_MODE_TABS.map(m => m.template))
foundry.applications.handlebars.loadTemplates(ROLL_PARTS.map(p => p.template))
ROLL_PARTS.forEach(p => p.onReady())
Handlebars.registerHelper('roll-centered-array', (base, show) => RollDialog.centeredArray(base, show))
Handlebars.registerHelper('roll-list-item-value', (list, key, path = undefined) => {
const selected = list.find(p => p.key == key)
if (selected && path && path != '') {
return foundry.utils.getProperty(selected, path)
}
return selected
})
Handlebars.registerHelper('roll-part-context', (rollData, code) => {
const rollPart = ROLL_PARTS.find(it => it.code == code)
if (rollPart == undefined) {
return {}
}
return {
code: code,
name: rollPart.name,
template: rollPart.template,
rollData: rollData,
refs: rollPart.getRefs(rollData),
current: rollPart.getCurrent(rollData)
}
})
}
static centeredArray(base, show) {
show = Math.abs(show)
const start = base - show
return [...Array(2 * show + 1).keys()].map(it => start + it)
}
static async create(rollData, rollOptions = {}) {
const rollDialog = new RollDialog(rollData, rollOptions)
rollDialog.render(true)
}
static get PARTS() {
return { form: { template: 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-dialog.hbs', } }
}
static get DEFAULT_OPTIONS() {
const default_options = {
tag: "form",
form: {
handler: RollDialog.handler,
submitOnChange: false,
closeOnSubmit: false
},
position: {
width: 600,
height: "auto",
},
}
return default_options
}
static async handler(event, form, formData) {
// rien pour l'instant
}
constructor(rollData, rollOptions) {
super()
this.rollData = rollData
// const callbacks = this.rollOptions.callbacks.map(c =>
// r => r.activve.actor Promise.all(this.rollOptions.callbacks.map(async callback => await callback(rollData.active.actor, rollData)))
// )
this.rollOptions = {
callbacks: [
async (actor, r) => await actor.appliquerAjoutExperience(r),
async (actor, r) => await actor.appliquerAppelMoral(r),
...(rollOptions.callbacks ?? [])
],
customChatMessage: rollOptions.customChatMessage,
onRoll: rollOptions.onRoll ?? doNothing
}
this.$loadParts()
}
/** pre-configure les paramètres des différentes parties de la fenêtre (par exemple, prépare les listes de caractéristiques/compétences */
$loadParts() {
const rollData = this.rollData;
const loadedMode = rollData.mode?.current
rollData.current = rollData.current ?? {}
rollData.selected = rollData.selected ?? {}
rollData.mode = rollData.mode ?? {}
rollData.mode.retry = rollData.mode.retry ?? false
BASIC_PARTS.restore(rollData)
rollData.mode.allowed = rollData.mode.retry ? [loadedMode] : rollData.mode.allowed ?? ROLL_MODE_TABS.map(m => m.code)
rollData.mode.current = loadedMode ?? ROLL_MODE_TABS.find(m => m.isAllowed(rollData) && m.visible(rollData))?.code ?? ROLL_MODE_COMP
this.getSelectedMode().setRollDataMode(rollData)
rollData.refs = this.$prepareRefs(rollData)
rollData.options = rollData.options ?? { showDice: true, rollMode: game.settings.get("core", "rollMode") }
ROLL_PARTS.forEach(p => p.initialize(rollData))
ROLL_PARTS.forEach(p => p.restore(rollData))
ROLL_PARTS.filter(p => p.isValid(rollData))
.forEach(p => {
p.loadRefs(rollData)
p.prepareContext(rollData)
})
this.selectMode();
}
selectMode() {
this.rollData.mode.label = this.getSelectedMode().title(this.rollData)
this.getSelectedMode().setRollDataMode(this.rollData)
this.getSelectedMode().onSelect(this.rollData);
}
$prepareRefs(rollData) {
return foundry.utils.mergeObject(rollData.refs ?? {}, Object.fromEntries(ROLL_PARTS.map(p => [p.code, {}])));
}
$saveParts() {
const target = BASIC_PARTS.initFrom(this.rollData)
ROLL_PARTS.filter(p => p.isActive(this.rollData))
.forEach(p => p.store(this.rollData, target))
return target
}
getActiveParts() {
return ROLL_PARTS.filter(p => p.isActive(this.rollData))
}
get title() {
return this.rollData.title ?? `Jet de dés de ${this.rollData.active.actor.name}`
}
async _onRender(context, options) {
this.window.title.innerText = this.rollTitle(this.rollData)
const buttonRoll = this.element.querySelector(`button[name="roll-dialog-button"]`)
buttonRoll?.addEventListener(
"click", e => {
e.preventDefault()
this.roll()
}
)
const buttonsMode = this.element.querySelectorAll(`button[name="roll-mode"]`)
buttonsMode?.forEach(it => it.addEventListener(
"click", e => {
e.preventDefault()
this.rollData.mode.current = e.currentTarget.dataset.mode
this.selectMode()
this.render()
}
))
Promise.all(
this.getActiveParts().map(async p => await p._onRender(this, context, options))
)
}
getAjustements() {
return this.getActiveParts()
.map(p => p.getAjustements(this.rollData))
.reduce((a, b) => a.concat(b))
.sort((a, b) => a.diff == undefined ? 1 : b.diff == undefined ? -1 : 0)
}
async buildHTMLTable(carac, diff) {
return await foundry.applications.handlebars.renderTemplate('roll-table', { carac, diff })
}
async _prepareContext() {
const rollData = this.rollData
const modes = ROLL_MODE_TABS.filter(m => m.isAllowed(rollData) && m.visible(rollData))
.map(m => m.toModeData(rollData))
this.setModeTitle()
const visibleRollParts = this.getActiveParts()
visibleRollParts.forEach(p => p.setExternalFilter(visibleRollParts, rollData))
this.setSpecialComp(visibleRollParts);
visibleRollParts.forEach(p => p.prepareContext(rollData))
this.calculAjustements()
const templates = this.getActiveParts().map(p => p.toTemplateData())
const context = await super._prepareContext()
return foundry.utils.mergeObject(
{
modes: modes,
templates: templates,
rollData: rollData,
}, context)
}
setSpecialComp(visibleRollParts) {
const specialComp = visibleRollParts.map(p => p.getSpecialComp(this.rollData))
.reduce((a, b) => a.concat(b))
if (specialComp.length > 0) {
const rollPartComp = this.getActiveParts()
.find(it => it.code == PART_COMP);
rollPartComp?.setSpecialComp(this.rollData, specialComp)
}
}
calculAjustements() {
this.rollData.ajustements = this.getAjustements()
this.rollData.ajustements.forEach(it => it.isDiff = it.diff != undefined)
this.rollData.current.totaldiff = this.rollData.ajustements
.map(adj => adj.diff)
.filter(d => d != undefined)
.reduce(Misc.sum(), 0)
}
setModeTitle() {
this.rollData.mode.label = this.getSelectedMode()?.title(this.rollData)
}
getSelectedMode() {
return ROLL_MODE_TABS.find(m => m.code == this.rollData.mode.current)
}
async roll() {
this.calculAjustements()
const rollData = this.rollData
console.info('Roll parts:', this.$saveParts())
const rolled = await this.$rollDice(rollData)
rollData.rolled = rolled
Promise.all(this.rollOptions.callbacks.map(async callback => await callback(rollData.active.actor, rollData)))
if (!this.rollOptions.customChatMessage) {
rollData.active.actor.$onRollCompetence(this.rollData)
}
this.rollOptions.onRoll(this)
}
async defaultCallback(rollData, rolled) {
await rollData.active.actor.appliquerAjoutExperience(rollData)
await rollData.active.actor.appliquerAppelMoral(rollData)
}
async $rollDice(rollData) {
const adapter = new RollDialogAdapter(ROLL_PARTS);
return await adapter.rollDice(rollData, this.rollTitle(rollData));
}
rollTitle(rollData) {
return ROLL_PARTS
.filter(it => it.section == ROLLDIALOG_SECTION.ACTION)
.filter(it => it.isActive(rollData))
.map(it => it.title(rollData))
.reduce(Misc.joining(' '))
}
}

View File

@@ -0,0 +1,13 @@
import { DIFF_MODE, ROLL_MODE_ATTAQUE } from "./roll-constants.mjs"
import { RollMode } from "./roll-mode.mjs"
export class RollModeAttaque extends RollMode {
get code() { return ROLL_MODE_ATTAQUE }
get name() { return `Attaquer` }
title(rollData) { return `attaque` }
onSelect(rollData) {
this.setDiffMode(rollData, DIFF_MODE.ATTAQUE)
}
}

View File

@@ -0,0 +1,9 @@
import { ROLL_MODE_COMP } from "./roll-constants.mjs"
import { RollMode } from "./roll-mode.mjs"
export class RollModeComp extends RollMode {
get code() { return ROLL_MODE_COMP }
get name() { return `Jet de caractéristique / compétence` }
title(rollData) { return `fait un jet ${rollData.mode.opposed ? ' contre ' : ''}` }
}

View File

@@ -0,0 +1,17 @@
import { DIFF_MODE, ROLL_MODE_DEFENSE } from "./roll-constants.mjs"
import { RollMode } from "./roll-mode.mjs"
export class RollModeDefense extends RollMode {
get code() { return ROLL_MODE_DEFENSE }
get name() { return `Se défendre` }
title(rollData) { return `se défend${rollData.attacker ? ' de' : ''}` }
getOpponent(rollData) {
return rollData.attacker
}
onSelect(rollData) {
this.setDiffMode(rollData, DIFF_MODE.DEFENSE)
}
}

View File

@@ -0,0 +1,17 @@
import { PART_JEU } from "./roll-part-jeu.mjs"
import { RollMode } from "./roll-mode.mjs"
import { ROLL_MODE_JEU } from "./roll-constants.mjs"
export class RollModeJeu extends RollMode {
get code() { return ROLL_MODE_JEU }
get name() { return `Jouer` }
visible(rollData) { return rollData.active.actor.isPersonnage() }
title(rollData) {
if (rollData.opponent) {
return `joue contre`
}
return `joue: ${rollData.current[PART_JEU].label}`
}
}

View File

@@ -0,0 +1,19 @@
import { DIFF_MODE, ROLL_MODE_MEDITATION } from "./roll-constants.mjs"
import { PART_MEDITATION } from "./roll-part-meditation.mjs"
import { RollMode } from "./roll-mode.mjs"
export class RollModeMeditation extends RollMode {
get code() { return ROLL_MODE_MEDITATION }
get name() { return `Méditation draconique` }
visible(rollData) { return rollData.active.actor.isHautRevant() }
title(rollData) {
const current = rollData.current[PART_MEDITATION]
const theme = current?.meditation.system.theme
return theme ? 'médite sur ' + theme : 'médite'
}
onSelect(rollData) {
this.setDiffMode(rollData, DIFF_MODE.AUCUN)
}
}

View File

@@ -0,0 +1,19 @@
import { DIFF_MODE, ROLL_MODE_OEUVRE } from "./roll-constants.mjs"
import { PART_OEUVRE } from "./roll-part-oeuvre.mjs"
import { RollMode } from "./roll-mode.mjs"
export class RollModeOeuvre extends RollMode {
get code() { return ROLL_MODE_OEUVRE }
get name() { return `Interpréter une oeuvre` }
visible(rollData) { return rollData.active.actor.isPersonnage() }
title(rollData) {
const current = rollData.current[PART_OEUVRE]
return `${current.art.action} ${current.label}`
}
onSelect(rollData) {
this.setDiffMode(rollData, DIFF_MODE.AUCUN)
}
}

View File

@@ -0,0 +1,15 @@
import { DIFF_MODE, ROLL_MODE_SORT } from "./roll-constants.mjs"
import { RollMode } from "./roll-mode.mjs"
import { PART_SORT } from "./roll-part-sort.mjs"
export class RollModeSort extends RollMode {
get code() { return ROLL_MODE_SORT }
get name() { return `lancer un sort` }
visible(rollData) { return rollData.active.actor.isHautRevant() }
title(rollData) { return `lance le sort:` }
onSelect(rollData) {
this.setDiffMode(rollData, DIFF_MODE.AUCUN)
}
}

View File

@@ -0,0 +1,19 @@
import { DIFF_MODE, ROLL_MODE_TACHE } from "./roll-constants.mjs"
import { PART_TACHE } from "./roll-part-tache.mjs"
import { RollMode } from "./roll-mode.mjs"
export class RollModeTache extends RollMode {
get code() { return ROLL_MODE_TACHE }
get name() { return `Travailler à une tâche` }
visible(rollData) { return rollData.active.actor.isPersonnage() }
title(rollData) {
const current = rollData.current[PART_TACHE]
const tache = current?.tache
return `travaille à sa tâche: ${tache.name ?? ''}`
}
onSelect(rollData) {
this.setDiffMode(rollData, DIFF_MODE.AUCUN)
}
}

54
module/roll/roll-mode.mjs Normal file
View File

@@ -0,0 +1,54 @@
import { DIFF_MODE } from "./roll-constants.mjs"
import { PART_DIFF } from "./roll-part-diff.mjs"
const DEFAULT_DIFF_MODES = [DIFF_MODE.LIBRE, DIFF_MODE.IMPOSEE, DIFF_MODE.DEFAUT]
export class RollMode {
onReady() { }
get code() { throw new Error(`Pas de code défini pour ${this}`) }
get name() { return this.code }
get icon() { return `systems/foundryvtt-reve-de-dragon/assets/actions/${this.code}.svg` }
toModeData(rollData) {
return { code: this.code, name: this.name, icon: this.icon, section: 'mode', template: this.template, selected: this.isSelected(rollData) }
}
isAllowed(rollData) { return rollData.mode.allowed == undefined || rollData.mode.allowed.includes(this.code) }
visible(rollData) { return true }
title(rollData) { return this.code }
isSelected(rollData) { return rollData.mode.current == this.code }
setRollDataMode(rollData) {
rollData.mode.opposed = rollData.opponent != undefined
rollData.mode.resistance = false /** TODO */
}
onSelect(rollData) {
const mode = [
rollData.current[PART_DIFF].mode,
this.modeFromOpponents(rollData),
rollData.selected[PART_DIFF].mode].find(m => DEFAULT_DIFF_MODES.includes(m))
this.setDiffMode(rollData, mode ??
DIFF_MODE.DEFAUT)
}
modeFromOpponents(rollData) {
if (rollData.mode.opposed) {
if (rollData.mode.resistance) {
return DIFF_MODE.IMPOSEE
}
return DIFF_MODE.LIBRE
}
return undefined
}
setDiffMode(rollData, mode) {
rollData.current[PART_DIFF].mode = mode
this.setRollDataMode(rollData)
}
}

View File

@@ -0,0 +1,19 @@
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
export const PART_ACTION = "action"
export class RollPartAction extends RollPart {
get code() { return PART_ACTION }
get section() { return ROLLDIALOG_SECTION.ACTION }
title(rollData) {
return rollData.mode.label
}
prepareContext(rollData) {
const current = this.getCurrent(rollData)
current.verb = rollData.mode.label
}
}

View File

@@ -0,0 +1,11 @@
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
export const PART_ACTOR = "actor"
export class RollPartActor extends RollPart {
get code() { return PART_ACTOR }
get section() { return ROLLDIALOG_SECTION.ACTION }
title(rollData) { return rollData.active.name }
}

View File

@@ -0,0 +1,42 @@
import { RdDCarac } from "../rdd-carac.js"
import { RollPartCheckbox } from "./roll-part-checkbox.mjs"
export const PART_APPELMORAL = "appelmoral"
export class RollPartAppelMoral extends RollPartCheckbox {
get code() { return PART_APPELMORAL }
get useCheckboxTemplate() { return false }
get isDefaultChecked() { return false }
visible(rollData) {
return rollData.active.actor.isPersonnage() && RdDCarac.isActionPhysique(rollData.current.carac.key)
}
restore(rollData) {
this.getCurrent(rollData).checked = this.getSaved(rollData).checked ?? false
}
store(rollData, targetData) {
this.setSaved(targetData, { checked: this.getCurrent(rollData).checked })
}
loadRefs(rollData) {
const refs = this.getRefs(rollData)
refs.moral = rollData.active.actor.getMoralTotal()
refs.label = refs.moral > 0 ? "Appel au moral" : "Énergie du désespoir"
}
getCheckboxIcon(rollData) {
const refs = this.getRefs(rollData)
if (refs.moral > 0) {
return '<i class="fa-regular fa-face-smile-beam"></i>'
}
if (refs.moral < 0) {
return '<i class="fa-regular fa-face-sad-tear"></i>'
}
return '<i class="fa-regular fa-face-meh"></i>'
}
getCheckboxLabel(rollData) { return "Appel au moral" }
getCheckboxValue(rollData) { return 1 }
}

View File

@@ -0,0 +1,33 @@
import { Grammar } from "../grammar.js"
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"
import { RollPartCheckbox } from "./roll-part-checkbox.mjs"
export const PART_ASTROLOGIQUE = "astrologique"
export class RollPartAstrologique extends RollPartCheckbox {
get code() { return PART_ASTROLOGIQUE }
get useCheckboxTemplate() { return false }
visible(rollData) {
return this.$isUsingAstrologie() && (
this.isJetChance(rollData)
|| this.isLancementRituel(rollData)
)
}
isLancementRituel(rollData) {
return false
}
isJetChance(rollData) {
return Grammar.includesLowerCaseNoAccent(rollData.current.carac.key, 'chance')
}
$isUsingAstrologie() {
return ReglesOptionnelles.isUsing("astrologie")
}
getCheckboxLabel(rollData) { return "Astrologique" }
getCheckboxValue(rollData) { return rollData.active.actor.ajustementAstrologique() }
}

View File

@@ -0,0 +1,66 @@
import { Grammar } from "../grammar.js"
import { ROLL_MODE_ATTAQUE } from "./roll-constants.mjs"
import { PART_CARAC } from "./roll-part-carac.mjs"
import { PART_COMP } from "./roll-part-comp.mjs"
import { RollPartSelect } from "./roll-part-select.mjs"
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
export const PART_ATTAQUE = 'attaque'
export class RollPartAttaque extends RollPartSelect {
get code() { return PART_ATTAQUE }
get section() { return ROLLDIALOG_SECTION.CHOIX }
visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_ATTAQUE) }
loadRefs(rollData) {
const refs = this.getRefs(rollData)
const attaques = rollData.active.actor.listAttaques()
refs.attaques = attaques.map(it => RollPartAttaque.$extractAttaque(it, rollData.active.actor))
if (refs.attaques.length>0){
this.$selectAttaque(rollData)
}
}
choices(refs) { return refs.attaques }
static $extractAttaque(action, actor) {
return {
key: `${action.action}::${action.arme.id}::${action.comp.id}`,
label: action.name,
action: action,
arme: action.arme,
comp: action.comp,
}
}
$selectAttaque(rollData, key) {
this.selectByKey(rollData, key, 0)
}
async _onRender(rollDialog, context, options) {
const selectAttaque = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-attaque"]`)
selectAttaque.addEventListener("change", e => {
const selectOptions = e.currentTarget.options
const index = selectOptions.selectedIndex
this.$selectAttaque(rollDialog.rollData, selectOptions[index]?.value)
rollDialog.setModeTitle()
rollDialog.render()
})
}
getExternalPartsFilter(partCode, rollData) {
if (this.visible(rollData)) {
const current = this.getCurrent(rollData)
switch (partCode) {
case PART_CARAC: return p => Grammar.equalsInsensitive(current.action.carac.key, p.key)
case PART_COMP: return p => p.label == current.comp?.name
}
}
return undefined
}
}

View File

@@ -0,0 +1,66 @@
import { RollPartSelect } from "./roll-part-select.mjs"
import { ROLLDIALOG_SECTION } from "./roll-part.mjs"
export const PART_CARAC = "carac"
export class RollPartCarac extends RollPartSelect {
/** TODO: remplacer selectOption par une sorte de sélecteur plus sympa? */
get code() { return PART_CARAC }
get name() { return 'Caractéristiques' }
get section() { return ROLLDIALOG_SECTION.CARAC }
loadRefs(rollData) {
const refs = this.getRefs(rollData)
refs.all = this.$getActorCaracs(rollData)
refs.caracs = refs.all
this.$selectCarac(rollData)
}
choices(refs) { return refs.caracs }
$getActorCaracs(rollData) {
return Object.entries(rollData.active.actor.getCarac())
.filter(([key, c]) => key != 'taille')
/* TODO: filter by context */
.map(([key, carac]) => {
return RollPartCarac.$extractCarac(key, carac)
})
}
static $extractCarac(key, carac) {
return {
key: key,
label: carac.label,
value: parseInt(carac.value)
}
}
setFilter(rollData, filter) {
const refs = this.getRefs(rollData)
refs.caracs = refs.all.filter(filter)
}
prepareContext(rollData) {
this.$selectCarac(rollData)
}
getAjustements(rollData) {
return []
}
async _onRender(rollDialog, context, options) {
const select = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select`)
select?.addEventListener("change", async e => {
const selectOptions = e.currentTarget.options
const index = selectOptions.selectedIndex
this.$selectCarac(rollDialog.rollData, selectOptions[index]?.value)
rollDialog.render()
})
}
$selectCarac(rollData, key) {
this.selectByKey(rollData, key, 10)
}
}

View File

@@ -0,0 +1,61 @@
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
export class RollPartCheckbox extends RollPart {
get section() { return ROLLDIALOG_SECTION.CONDITIONS }
get useCheckboxTemplate() { return true }
get template() { return this.useCheckboxTemplate ? 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-part-checkbox.hbs' : super.template }
get isDefaultChecked() { return true }
restore(rollData) {
const checked = this.getSaved(rollData).checked
this.getCurrent(rollData).checked = checked == undefined? this.isDefaultChecked : checked
}
store(rollData, targetData) {
this.setSaved(targetData, { checked: this.getCurrent(rollData).checked })
}
loadRefs(rollData) {
const current = this.getCurrent(rollData)
current.label = this.getCheckboxLabel(rollData)
}
prepareContext(rollData) {
const current = this.getCurrent(rollData)
if (current.checked == undefined) {
/* TODO: user setting? */
current.checked = true
}
if (current.value == undefined) {
current.value = this.getCheckboxValue(rollData)
}
current.icon = this.getCheckboxIcon(rollData)
}
getAjustements(rollData) {
const current = this.getCurrent(rollData)
if (current.checked) {
return [{ label: this.getCheckboxLabelAjustement(rollData), diff: current.value }]
}
return []
}
getCheckboxLabelAjustement(rollData) {
return `${this.getCheckboxIcon(rollData)} ${this.getCurrent(rollData).label}`
}
async _onRender(rollDialog, context, options) {
const checkbox = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${this.code}"]`)
checkbox?.addEventListener("change", e => {
this.getCurrent(rollDialog.rollData).checked = e.currentTarget.checked
rollDialog.render()
})
}
getCheckboxIcon(rollData) { return '' }
getCheckboxLabel(rollData) { return "LABEL" }
getCheckboxValue(rollData) { return 0 }
}

View File

@@ -0,0 +1,66 @@
import { RdDCarac } from "../rdd-carac.js"
import { RollPartSelect } from "./roll-part-select.mjs"
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
const COEUR = "coeur"
const SANS_AMOUR = { key: '', label: "", value: 0 }
export class RollPartCoeur extends RollPartSelect {
get code() { return COEUR }
get section() { return ROLLDIALOG_SECTION.CONDITIONS }
get useCheckboxTemplate() { return false }
isValid(rollData) { return rollData.active.actor.isPersonnage() }
visible(rollData) {
return this.getRefs(rollData).amoureux.length > 1 && RdDCarac.isVolonte(rollData.current.carac.key)
}
loadRefs(rollData) {
const liste = rollData.active.actor.listeAmoureux()
.filter(amour => amour.coeur > 0)
.map(RollPartCoeur.$extractAmoureux)
this.getRefs(rollData).amoureux = [SANS_AMOUR, ...liste]
this.$selectAmoureux(rollData)
}
choices(refs) { return refs.amoureux }
static $extractAmoureux(amour) {
return {
key: amour.id,
label: amour.name,
value: -2 * (amour?.coeur ?? 0),
amour: amour
}
}
$selectAmoureux(rollData, key) {
this.selectByKey(rollData, key, 0)
}
getAjustements(rollData) {
const current = this.getCurrent(rollData)
if (current.key != '') {
return [{
label: "Coeur pour " + current.label,
diff: current.value
}]
}
return []
}
async _onRender(rollDialog, context, options) {
const selectAmour = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="${this.code}"]`)
selectAmour?.addEventListener("change", e => {
const selectOptions = e.currentTarget.options
const index = selectOptions.selectedIndex
this.$selectAmoureux(rollDialog.rollData, selectOptions[index]?.value)
rollDialog.render()
})
}
}

View File

@@ -0,0 +1,77 @@
import { Grammar } from "../grammar.js"
import { Misc } from "../misc.js"
import { RollPartSelect } from "./roll-part-select.mjs"
import { ROLLDIALOG_SECTION } from "./roll-part.mjs"
export const PART_COMP = "comp"
const SANS_COMPETENCE = { key: '', label: "Sans compétence", value: 0 }
export class RollPartComp extends RollPartSelect {
/** TODO: remplacer selectOption par un sélecteur plus sympa (avec image de compétence, par exemple? */
get code() { return PART_COMP }
get name() { return 'Compétences' }
get section() { return ROLLDIALOG_SECTION.COMP }
loadRefs(rollData) {
const refs = this.getRefs(rollData)
refs.all = this.$getActorComps(rollData)
refs.comps = refs.all
this.$selectComp(rollData)
}
choices(refs) { return refs.comps }
$getActorComps(rollData) {
const competences = (rollData.active.actor?.getCompetences() ?? [])
.map(RollPartComp.$extractComp)
.sort(Misc.ascending(it => Grammar.toLowerCaseNoAccentNoSpace(it.label)))
/* TODO: filter competences */
const listCompetences = [
SANS_COMPETENCE,
...competences
]
return listCompetences
}
static $extractComp(comp) {
return {
key: comp.name,
label: comp.name,
value: comp.system.niveau,
comp: comp
}
}
setFilter(rollData, filter) {
const refs = this.getRefs(rollData)
refs.comps = refs.all.filter(filter)
}
prepareContext(rollData) {
this.$selectComp(rollData)
}
setSpecialComp(rollData, comps) {
this.getRefs(rollData).comps = comps.map(RollPartComp.$extractComp)
.sort(Misc.ascending(it => Grammar.toLowerCaseNoAccentNoSpace(it.label)))
}
async _onRender(rollDialog, context, options) {
const select = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select`)
select?.addEventListener("change", e => {
const selectOptions = e.currentTarget.options
const index = selectOptions.selectedIndex
this.$selectComp(rollDialog.rollData, selectOptions[index]?.value)
rollDialog.render()
})
}
$selectComp(rollData, key) {
this.selectByKey(rollData, key, 0)
}
}

View File

@@ -0,0 +1,74 @@
import { SYSTEM_RDD } from "../constants.js";
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs";
const CONDITIONS = "conditions"
const DESCR_CONDITIONS = "Conditions"
export class RollPartConditions extends RollPart {
/** TODO: use alternate to numberInput that supports displaying '+' sign */
settingMin() { return RollPart.settingKey(this, 'min') }
settingMax() { return RollPart.settingKey(this, 'max') }
onReady() {
game.settings.register(SYSTEM_RDD, this.settingMin(),
{
name: "Malus maximal de conditions",
type: Number,
config: true,
scope: "world",
range: { min: -20, max: -10, step: 1 },
default: -16
}
)
game.settings.register(SYSTEM_RDD, this.settingMax(),
{
name: "Bonus maximal de conditions",
type: Number,
config: true,
scope: "world",
range: { min: 5, max: 15, step: 1 },
default: 10
}
)
}
restore(rollData) {
const current = this.getCurrent(rollData)
current.value = this.getSaved(rollData)?.value ?? current.value ?? 0
}
store(rollData, targetData) {
this.setSaved(targetData, { value: this.getCurrent(rollData).value })
}
/** @override */
get code() { return CONDITIONS }
get section() { return ROLLDIALOG_SECTION.CONDITIONS }
prepareContext(rollData) {
const current = this.getCurrent(rollData)
current.min = game.settings.get(SYSTEM_RDD, this.settingMin())
current.max = game.settings.get(SYSTEM_RDD, this.settingMax())
current.value = current.value ?? 0
}
getAjustements(rollData) {
const current = this.getCurrent(rollData)
if (current.value != 0) {
return [{ label: DESCR_CONDITIONS, diff: current.value }]
}
return []
}
async _onRender(rollDialog, context, options) {
const input = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${this.code}"]`)
input?.addEventListener("change", e => {
const current = this.getCurrent(rollDialog.rollData)
current.value = parseInt(e.currentTarget.value)
rollDialog.render()
})
}
}

View File

@@ -0,0 +1,17 @@
import { ROLL_MODE_DEFENSE } from "./roll-constants.mjs"
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
export const PART_DEFENSE = 'defense'
export class RollPartDefense extends RollPart {
get code() { return PART_DEFENSE }
get section() { return ROLLDIALOG_SECTION.CHOIX }
visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_DEFENSE) }
loadRefs(rollData) {
const refs = this.getRefs(rollData)
refs.defenses =[]
}
}

View File

@@ -0,0 +1,70 @@
import { DIFF_MODE, DIFF_MODES, ROLL_MODE_MEDITATION, ROLL_MODE_OEUVRE, ROLL_MODE_SORT, ROLL_MODE_TACHE } from "./roll-constants.mjs";
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs";
export const PART_DIFF = "diff"
const EXCLUDED_ROLL_MODES = [ROLL_MODE_TACHE, ROLL_MODE_MEDITATION, ROLL_MODE_SORT, ROLL_MODE_OEUVRE]
export class RollPartDiff extends RollPart {
get code() { return PART_DIFF }
get section() { return ROLLDIALOG_SECTION.CHOIX }
restore(rollData) {
const current = this.getCurrent(rollData)
const saved = this.getSaved(rollData)
current.value = saved?.value ?? current.value ?? 0
current.mode = saved?.mode ?? current.mode
}
store(rollData, targetData) {
const current = this.getCurrent(rollData)
this.setSaved(targetData, {
value: current.value,
mode: current.mode
})
}
visible(rollData) {
if (EXCLUDED_ROLL_MODES.includes(rollData.mode.current)) {
return false
}
const current = this.getCurrent(rollData)
/* TODO: affiner les cas où afficher ou non. devrait s'afficher pour les jets basiques (même si pas d'opposant sélectionné)*/
return Object.values(DIFF_MODE).includes(current.mode)
}
prepareContext(rollData) {
const current = this.getCurrent(rollData)
const diffMode = DIFF_MODES[current.mode] ?? DIFF_MODES[DIFF_MODE.AUCUN]
foundry.utils.mergeObject(current,
{
mode: diffMode.key,
label: diffMode?.label ?? '',
value: current.value ?? 0,
disabled: !diffMode.libre,
min: -10,
max: diffMode.max
},
{ inplace: true }
)
}
getAjustements(rollData) {
const current = this.getCurrent(rollData)
return [{
label: current.label,
diff: current.value
}]
}
async _onRender(rollDialog, context, options) {
const input = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${this.code}"]`)
input?.addEventListener("change", e => {
this.getCurrent(rollDialog.rollData).value = parseInt(e.currentTarget.value)
rollDialog.render()
})
}
}

View File

@@ -0,0 +1,31 @@
import { RdDItemCompetence } from "../item-competence.js"
import { RdDCarac } from "../rdd-carac.js"
import { RollPartCheckbox } from "./roll-part-checkbox.mjs"
const ENCTOTAL = "enctotal"
export class RollPartEncTotal extends RollPartCheckbox {
get code() { return ENCTOTAL }
get useCheckboxTemplate() { return false }
visible(rollData) {
return RdDCarac.isAgiliteOuDerobee(rollData.current.carac.key)
&& RdDItemCompetence.isMalusEncombrementTotal(rollData.current.comp?.key)
}
async _onRender(rollDialog, context, options) {
super._onRender(rollDialog, context, options)
const inputMalusEnc = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="malusenc"]`)
inputMalusEnc?.addEventListener("change", e => {
this.getCurrent(rollDialog.rollData).value = parseInt(e.currentTarget.value)
rollDialog.render()
})
}
getCheckboxIcon(rollData) { return '<i class="fa-solid fa-weight-hanging"></i>' }
getCheckboxLabel(rollData) { return "Enc. total" }
getCheckboxValue(rollData) { return - rollData.active.actor.getEncTotal() }
}

View File

@@ -0,0 +1,30 @@
import { RdDCarac } from "../rdd-carac.js"
import { RollPartCheckbox } from "./roll-part-checkbox.mjs"
const ETAT = "etat"
export class RollPartEtat extends RollPartCheckbox {
get code() { return ETAT }
visible(rollData) {
const selectedCarac = rollData.current.carac?.key ?? ''
if (selectedCarac == '') {
return false
}
if (RdDCarac.isChance(selectedCarac)) {
return false
}
if (RdDCarac.isReve(selectedCarac)) {
if ((rollData.current.comp?.key ?? '') == '') {
return false
}
}
return this.getCheckboxValue(rollData) != 0
}
getCheckboxLabel(rollData) { return "État général" }
getCheckboxValue(rollData) {
return rollData.active.actor.getEtatGeneral({ ethylisme: true })
}
}

View File

@@ -0,0 +1,28 @@
import { RdDCarac } from "../rdd-carac.js"
import { RdDUtility } from "../rdd-utility.js"
import { RollPartCheckbox } from "./roll-part-checkbox.mjs"
const ETHYLISME = "ethylisme"
export class RollPartEthylisme extends RollPartCheckbox {
get code() { return ETHYLISME }
isValid(rollData) { return rollData.active.actor.isPersonnage()}
visible(rollData) {
return rollData.active.actor.isAlcoolise() && !RdDCarac.isChance(rollData.current.carac.key)
}
getCheckboxIcon(rollData) {
return '<i class="fa-solid fa-champagne-glasses"></i>'
}
getCheckboxLabel(rollData) {
return `${RdDUtility.getNomEthylisme(rollData.active.actor.ethylisme())}`
}
getCheckboxValue(rollData) {
return rollData.active.actor.malusEthylisme()
}
}

View File

@@ -0,0 +1,110 @@
import { Grammar } from "../grammar.js"
import { ITEM_TYPES } from "../constants.js"
import { CARACS } from "../rdd-carac.js"
import { ROLL_MODE_JEU } from "./roll-constants.mjs"
import { PART_CARAC } from "./roll-part-carac.mjs"
import { PART_COMP } from "./roll-part-comp.mjs"
import { RollPartSelect } from "./roll-part-select.mjs"
import { ROLLDIALOG_SECTION } from "./roll-part.mjs"
export const PART_JEU = "jeu"
const COMPETENCE_JEU = 'Jeu'
export class RollPartJeu extends RollPartSelect {
get code() { return PART_JEU }
get section() { return ROLLDIALOG_SECTION.CHOIX }
isValid(rollData) { return rollData.active.actor.isPersonnage() }
visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_JEU) }
loadRefs(rollData) {
const refs = this.getRefs(rollData)
refs.jeux = rollData.active.actor.itemTypes[ITEM_TYPES.jeu]
.map(it => RollPartJeu.$extractJeu(it, rollData.active.actor))
if (refs.jeux.length > 0) {
this.$selectJeu(rollData)
}
}
choices(refs) { return refs.jeux }
static $extractJeu(jeu, actor) {
const comp = actor.getCompetence(COMPETENCE_JEU)
const caracs = jeu.system.caraccomp.toLowerCase().split(/[.,:\/-]/).map(it => it.trim())
const base = RollPartJeu.$getJeuBase(jeu, comp, caracs)
return {
key: jeu.id,
label: jeu.name,
caracs: caracs,
jeu: jeu,
value: (base ?? comp).system.niveau,
base: base,
comp: comp
}
}
static $getJeuBase(jeu, comp, caracs) {
if (jeu.system.base < comp.system.niveau) {
return undefined
}
return {
id: comp.id,
name: `Jeu ${jeu.name}`,
type: comp.type,
img: comp.img,
system: foundry.utils.mergeObject(
{
niveau: jeu.system.base,
base: jeu.system.base,
default_carac: caracs.length > 0 ? caracs[0] : CARACS.CHANCE
},
comp.system,
{ inplace: true, overwrite: false }
)
}
}
prepareContext(rollData) {
const current = this.getCurrent(rollData)
if (rollData.mode.current == ROLL_MODE_JEU && current) {
rollData.mode.opposed = true
}
}
getAjustements(rollData) { return [] }
$selectJeu(rollData, key) {
this.selectByKey(rollData, key, 0)
}
async _onRender(rollDialog, context, options) {
const selectjeu = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-jeu"]`)
selectjeu.addEventListener("change", e => {
const selectOptions = e.currentTarget.options
const index = selectOptions.selectedIndex
this.$selectJeu(rollDialog.rollData, selectOptions[index]?.value)
rollDialog.setModeTitle()
rollDialog.render()
})
}
getExternalPartsFilter(partCode, rollData) {
if (this.visible(rollData)) {
const current = this.getCurrent(rollData)
switch (partCode) {
case PART_CARAC: return p => current.caracs?.includes(Grammar.toLowerCaseNoAccent(p.key))
case PART_COMP: return p => p.label == current.comp?.name
}
}
return undefined
}
getSpecialComp(rollData) {
const current = this.getCurrent(rollData)
return current.base ? [current.base] : []
}
}

View File

@@ -0,0 +1,16 @@
import { RdDCarac } from "../rdd-carac.js"
import { RollPartCheckbox } from "./roll-part-checkbox.mjs"
const MALUSARMURE = "malusarmure"
export class RollPartMalusArmure extends RollPartCheckbox {
get code() { return MALUSARMURE }
visible(rollData) {
return RdDCarac.isAgiliteOuDerobee(rollData.current.carac.key) && this.getCheckboxValue(rollData) != 0
}
getCheckboxLabel(rollData) { return "Malus armure" }
getCheckboxValue(rollData) { return rollData.active.actor.getMalusArmure() }
}

View File

@@ -0,0 +1,117 @@
import { ITEM_TYPES } from "../constants.js"
import { Grammar } from "../grammar.js"
import { RdDCarac } from "../rdd-carac.js"
import { RdDTimestamp } from "../time/rdd-timestamp.js"
import { TMRUtility } from "../tmr-utility.js"
import { ROLL_MODE_MEDITATION } from "./roll-constants.mjs"
import { PART_CARAC } from "./roll-part-carac.mjs"
import { PART_COMP } from "./roll-part-comp.mjs"
import { RollPartSelect } from "./roll-part-select.mjs"
import { ROLLDIALOG_SECTION } from "./roll-part.mjs"
export const PART_MEDITATION = "meditation"
export class RollPartMeditation extends RollPartSelect {
get code() { return PART_MEDITATION }
get section() { return ROLLDIALOG_SECTION.CHOIX }
isValid(rollData) { return rollData.active.actor.isPersonnage() && rollData.active.actor.isHautRevant() }
visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_MEDITATION) }
loadRefs(rollData) {
const refs = this.getRefs(rollData)
foundry.utils.mergeObject(refs,
{
meditations: rollData.active.actor.itemTypes[ITEM_TYPES.meditation]
.map(it => RollPartMeditation.$extractMeditation(it, rollData.active.actor))
}
)
if (refs.meditations.length > 0) {
this.$selectMeditation(rollData)
}
}
choices(refs) { return refs.meditations }
static $extractMeditation(meditation, actor) {
return {
key: meditation.id,
label: meditation.name,
meditation: meditation,
comp: actor.getCompetence(meditation.system.competence)
}
}
prepareContext(rollData) {
this.getCurrent(rollData).value = this.getMalusConditions(rollData)
}
getMalusConditions(rollData) {
const current = this.getCurrent(rollData)
const conditionsManquantes = [
current.isComportement,
current.isHeure,
current.isPurification,
current.isVeture
].filter(it => !it).length
return -2 * conditionsManquantes
}
getMalusEchecs(rollData) {
return this.getCurrent(rollData).meditation.system.malus
}
getAjustements(rollData) {
const malusEchecs = { label: "Méditation", diff: this.getMalusEchecs(rollData) }
const malusConditions = { label: "Conditions", diff: this.getMalusConditions(rollData) }
return [malusConditions, ...(malusEchecs.diff == 0 ? [] : [malusEchecs])]
}
$selectMeditation(rollData, key) {
const previous = this.getCurrent(rollData)
const current = this.selectByKey(rollData, key, 0)
if (current.key != previous.key) {
const heureMonde = RdDTimestamp.getWorldTime().heure
const heureMeditation = RdDTimestamp.findHeure(current.meditation.system.heure)?.heure
current.isHeure = heureMeditation == heureMonde
current.isTMR = Grammar.equalsInsensitive(current.meditation.system.tmr, TMRUtility.getTMRType(rollData.active.actor.system.reve.tmrpos.coord))
}
}
async _onRender(rollDialog, context, options) {
const selectMeditation = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-meditation"]`)
selectMeditation.addEventListener("change", e => {
const selectOptions = e.currentTarget.options
const index = selectOptions.selectedIndex
this.$selectMeditation(rollDialog.rollData, selectOptions[index]?.value)
rollDialog.render()
rollDialog.setModeTitle()
})
this.setupListenerCondition(rollDialog, 'isComportement')
this.setupListenerCondition(rollDialog, 'isHeure')
this.setupListenerCondition(rollDialog, 'isPurification')
this.setupListenerCondition(rollDialog, 'isVeture')
}
setupListenerCondition(rollDialog, inputName) {
const checkbox = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${inputName}"]`)
checkbox.addEventListener("change", e => {
const current = this.getCurrent(rollDialog.rollData)
current[inputName] = e.currentTarget.checked
rollDialog.render()
})
}
getExternalPartsFilter(partCode, rollData) {
if (this.visible(rollData)) {
const current = this.getCurrent(rollData)
switch (partCode) {
case PART_CARAC: return p => RdDCarac.isIntellect(p.key)
case PART_COMP: return p => p.label == current.comp?.name
}
}
return undefined
}
}

View File

@@ -0,0 +1,16 @@
import { RdDCarac } from "../rdd-carac.js"
import { RollPartCheckbox } from "./roll-part-checkbox.mjs"
const MORAL = "moral"
export class RollPartMoral extends RollPartCheckbox {
get code() { return MORAL }
visible(rollData) {
return RdDCarac.isVolonte(rollData.current.carac.key) && this.getCheckboxValue(rollData) != 0
}
getCheckboxLabel(rollData) { return "Moral" }
getCheckboxValue(rollData) { return rollData.active.actor.getMoralTotal() }
}

View File

@@ -0,0 +1,99 @@
import { ITEM_TYPES } from "../constants.js"
import { Grammar } from "../grammar.js"
import { Misc } from "../misc.js"
import { CARACS } from "../rdd-carac.js"
import { ROLL_MODE_OEUVRE } from "./roll-constants.mjs"
import { PART_CARAC } from "./roll-part-carac.mjs"
import { PART_COMP } from "./roll-part-comp.mjs"
import { RollPartSelect } from "./roll-part-select.mjs"
import { ROLLDIALOG_SECTION } from "./roll-part.mjs"
export const PART_OEUVRE = "oeuvre"
const ARTS = [
{ type: ITEM_TYPES.oeuvre, action: "interpréte l'oeuvre", competence: it => it.system.competence, caracs: it => [it.system.default_carac] },
{ type: ITEM_TYPES.chant, action: "chante", competence: it => 'Chant', caracs: it => [CARACS.OUIE] },
{
type: ITEM_TYPES.danse, action: "danse:", competence: it => 'Danse', caracs: it => {
const caracs = []
if (it.system.agilite) { caracs.push(CARACS.AGILITE) }
if (it.system.apparence) { caracs.push(CARACS.APPARENCE) }
return caracs
}
},
{ type: ITEM_TYPES.musique, action: "joue le morceau:", competence: it => 'Musique', caracs: it => [CARACS.OUIE] },
{ type: ITEM_TYPES.recettecuisine, action: "cuisine le plat:", competence: it => 'Cuisine', caracs: it => [CARACS.ODORATGOUT] },
]
export class RollPartOeuvre extends RollPartSelect {
onReady() {
ARTS.forEach(art => art.label = Misc.typeName('Item', art.type))
ARTS.map(it => `roll-oeuvre-${it.type}`)
.forEach(art =>
foundry.applications.handlebars.loadTemplates({ [art]: `systems/foundryvtt-reve-de-dragon/templates/roll/${art}.hbs` })
)
}
get code() { return PART_OEUVRE }
get section() { return ROLLDIALOG_SECTION.CHOIX }
isValid(rollData) { return rollData.active.actor.isPersonnage() }
visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_OEUVRE) }
loadRefs(rollData) {
const refs = this.getRefs(rollData)
refs.oeuvres = rollData.active.actor.items
.filter(it => it.isOeuvre() && RollPartOeuvre.getArt(it))
.map(it => RollPartOeuvre.$extractOeuvre(it, rollData.active.actor))
if (refs.oeuvres.length > 0) {
this.$selectOeuvre(rollData)
}
}
choices(refs) { return refs.oeuvres }
static $extractOeuvre(oeuvre, actor) {
const art = RollPartOeuvre.getArt(oeuvre)
return {
key: oeuvre.id,
label: oeuvre.name,
art: art,
caracs: art.caracs(oeuvre),
value: -oeuvre.system.niveau,
oeuvre: oeuvre,
comp: actor.getCompetence(art.competence(oeuvre))
}
}
static getArt(oeuvre) {
return ARTS.find(it => it.type == oeuvre.type)
}
$selectOeuvre(rollData, key) {
this.selectByKey(rollData, key, 0)
}
async _onRender(rollDialog, context, options) {
const selectOeuvre = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-oeuvre"]`)
selectOeuvre.addEventListener("change", e => {
const selectOptions = e.currentTarget.options
const index = selectOptions.selectedIndex
this.$selectOeuvre(rollDialog.rollData, selectOptions[index]?.value)
rollDialog.setModeTitle()
rollDialog.render()
})
}
getExternalPartsFilter(partCode, rollData) {
if (this.visible(rollData)) {
const current = this.getCurrent(rollData)
switch (partCode) {
case PART_CARAC: return p => current.caracs?.includes(Grammar.toLowerCaseNoAccent(p.key))
case PART_COMP: return p => p.label == current.comp?.name
}
}
return undefined
}
}

View File

@@ -0,0 +1,12 @@
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
const OPPONENT = "opponent"
export class RollPartOpponent extends RollPart {
get code() { return OPPONENT }
get section() { return ROLLDIALOG_SECTION.ACTION }
visible(rollData) { return rollData.mode.opposed }
title(rollData) { return rollData.opponent?.name ?? '' }
}

View File

@@ -0,0 +1,32 @@
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
const ROLLMODE = "rollmode"
export class RollPartRollMode extends RollPart {
get code() { return ROLLMODE }
get section() { return ROLLDIALOG_SECTION.CONDITIONS }
loadRefs(rollData) {
const refs = this.getRefs(rollData)
refs.rollmodes = Object.entries(CONFIG.Dice.rollModes).map(([k, v]) => { return { key: k, label: v.label, icon: v.icon } })
}
restore(rollData) {
this.setCurrent(rollData, { key: this.getSaved(rollData)?.key ?? game.settings.get("core", "rollMode") })
}
store(rollData, targetData) {
this.setSaved(targetData, { key: this.getCurrent(rollData).key })
}
async _onRender(rollDialog, context, options) {
const rollvisibilityButtons = rollDialog.element.querySelectorAll(`button[name="roll-rollmode"]`)
rollvisibilityButtons?.forEach(it => it.addEventListener(
"click", e => {
e.preventDefault()
this.getCurrent(rollDialog.rollData).key = e.currentTarget.dataset.key
rollDialog.render()
}))
}
}

View File

@@ -0,0 +1,50 @@
import { Grammar } from "../grammar.js"
import { RollPart } from "./roll-part.mjs"
export class RollPartSelect extends RollPart {
restore(rollData) {
this.setCurrent(rollData, { key: this.getSaved(rollData)?.key })
}
store(rollData, targetData) {
this.setSaved(targetData, { key: this.getCurrent(rollData).key })
}
choices(refs) { return [] }
getAjustements(rollData) {
const current = this.getCurrent(rollData)
if (current) {
return [{ label: current.label, diff: current.value }]
}
return []
}
selectByKey(rollData, key = undefined, defValue = undefined) {
const refs = this.getRefs(rollData)
const choices = this.choices(refs) ??[]
const current = this.getCurrent(rollData)
const newChoice = (choices.length == 0)
? { key: '', value: defValue }
: this.$getSelectedChoice(choices, key ?? current?.key ?? refs.key ?? '')
this.setCurrent(rollData, newChoice)
return newChoice
}
$getSelectedChoice(choices, key) {
const potential = choices.filter(it => Grammar.includesLowerCaseNoAccent(it.key, key))
switch (potential.length) {
case 0:
// ui.notifications.warn(`Aucun choix de ${this.name} pour ${key}`)
return choices[0]
case 1:
return potential[0]
default:
const selected = potential.find(it => Grammar.equalsInsensitive(it.key, key))
// ui.notifications.info(`Plusieurs choix de ${this.name} pour ${key}, le premier est utilisé`)
return selected ?? potential[0]
}
}
}

View File

@@ -0,0 +1,93 @@
import { Misc } from "../misc.js"
import { StatusEffects } from "../settings/status-effects.js"
import { ROLL_MODE_ATTAQUE, ROLL_MODE_DEFENSE } from "./roll-constants.mjs"
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
export const PART_SIGN = "sign"
export class RollPartSign extends RollPart {
get code() { return PART_SIGN }
get section() { return ROLLDIALOG_SECTION.AJUSTEMENTS }
loadRefs(rollData) {
this.setFromState(rollData)
}
restore(rollData) {
this.setCurrent(rollData, this.getSaved(rollData))
}
store(rollData, targetData) {
this.setSaved(targetData, this.getCurrent(rollData))
}
// visible(rollData) {
// const current = this.getCurrent(rollData)
// return current.surprise != ''
// }
isCombat(rollData) {
return [ROLL_MODE_ATTAQUE, ROLL_MODE_DEFENSE].includes(rollData.mode.current) || rollData.mode.isCombat
}
prepareContext(rollData) {
this.setFromState(rollData)
}
setFromState(rollData) {
if (rollData.mode.retry) {
return
}
const actor = rollData.active.actor;
const isCombat = this.isCombat(rollData)
const current = this.getCurrent(rollData)
current.surprise = actor.getSurprise(isCombat)
current.reasons = actor.getEffects(it => StatusEffects.niveauSurprise(it) > 0).map(it => it.name)
current.diviseur = 1
if (isCombat && actor.isDemiReve()) {
current.reasons.push('Demi-rêve en combat')
}
if (current.surprise == 'demi') {
current.diviseur *= 2
}
if (this.isAttaqueFinesse(rollData)) {
current.diviseur *= 2
current.reasons.push('Attaque en finesse')
}
if (this.isForceInsuffisante(rollData)) {
current.diviseur *= 2
current.reasons.push('Force insuffisante')
}
current.reason = current.reasons.join(', ')
}
isForceInsuffisante(rollData) {
//this.isCombat(rollData) && ... arme avec force min
return this.isCombat(rollData) && true
}
isAttaqueFinesse(rollData) {
// this.rollData.selected[PART_DEFENSE] && attaquant avec particulière en finesse
return ROLL_MODE_DEFENSE == rollData.mode.current && true
}
getAjustements(rollData) {
const current = this.getCurrent(rollData)
if (current.surprise == 'demi') {
return [{ label: 'Significative requise ' + Misc.getFractionOneN(current.diviseur), diff: undefined }]
}
return []
}
async _onRender(rollDialog, context, options) {
const input = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${this.code}"]`)
input?.addEventListener("change", e => {
this.getCurrent(rollDialog.rollData).value = parseInt(e.currentTarget.value)
rollDialog.render()
})
}
}

View File

@@ -0,0 +1,129 @@
import { ITEM_TYPES } from "../constants.js"
import { ROLL_MODE_SORT } from "./roll-constants.mjs"
import { PART_CARAC } from "./roll-part-carac.mjs"
import { PART_COMP } from "./roll-part-comp.mjs"
import { ROLLDIALOG_SECTION } from "./roll-part.mjs"
import { TMRUtility } from "../tmr-utility.js"
import { RdDItemSort } from "../item-sort.js"
import { RollPartSelect } from "./roll-part-select.mjs"
export const PART_SORT = "sort"
export class RollPartSort extends RollPartSelect {
onReady() {
// TODO: utiliser un hook pour écouter les déplacements dans les TMRs?
}
get code() { return PART_SORT }
get section() { return ROLLDIALOG_SECTION.CHOIX }
isValid(rollData) { return rollData.active.actor.isPersonnage() && rollData.active.actor.isHautRevant() }
visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_SORT) }
loadRefs(rollData) {
const refs = this.getRefs(rollData)
const coord = rollData.active.actor.system.reve.tmrpos.coord
const draconics = rollData.active.actor.getDraconicList()
const sorts = rollData.active.actor.itemTypes[ITEM_TYPES.sort]
.map(s => RollPartSort.$extractSort(s, coord, draconics))
foundry.utils.mergeObject(refs,
{
coord: coord,
tmr: TMRUtility.getTMR(coord),
reve: rollData.active.actor.system.reve.reve.value,
draconics: draconics,
all: sorts,
sorts: sorts.filter(it => RdDItemSort.isSortOnCoord(it.sort, coord))
},
{ inplace: true }
)
if (refs.sorts.length > 0) {
this.$selectSort(rollData)
}
}
choices(refs) { return refs.sorts }
static $extractSort(sort, coord, draconics) {
const isDiffVariable = RdDItemSort.isDifficulteVariable(sort)
const isReveVariable = RdDItemSort.isCoutVariable(sort)
return {
key: sort.id,
label: sort.name,
value: isDiffVariable ? -7 : parseInt(sort.system.difficulte),
ptreve: isReveVariable ? 1 : sort.system.ptreve,
caseTMR: RdDItemSort.getCaseTMR(sort),
isDiffVariable: isDiffVariable,
isReveVariable: isReveVariable,
isReserve: false,
sort: sort,
draconics: RdDItemSort.getDraconicsSort(draconics, sort).map(it => it.name)
}
}
getAjustements(rollData) {
const current = this.getCurrent(rollData)
if (current) {
const reserve = current.isReserve ?
[{ label: `Mise en réserve en ${current.coord}` }] : []
const bonusCase = current.bonusCase ?
[{ label: `Bonus case +${current.bonusCase}%` }] : []
return [
{ label: current.label, diff: current.value },
{ label: `r${current.ptreve}` },
...bonusCase,
...reserve
]
}
return []
}
$selectSort(rollData, key) {
const previous = this.getCurrent(rollData)
const current = this.selectByKey(rollData, key, -7)
if (current.key != previous.key) { }
current.bonusCase = RdDItemSort.getCaseBonus(current.sort,
rollData.active.actor.system.reve.tmrpos.coord)
}
async _onRender(rollDialog, context, options) {
const current = this.getCurrent(rollDialog.rollData)
const selectSort = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-sort"]`)
const inputDiff = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="diff-var"]`)
const inputPtReve = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="ptreve-var"]`)
const checkboxReserve = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="reserve"]`)
selectSort.addEventListener("change", e => {
const selectOptions = e.currentTarget.options
const index = selectOptions.selectedIndex
this.$selectSort(rollDialog.rollData, selectOptions[index]?.value)
rollDialog.setModeTitle()
rollDialog.render()
})
inputDiff?.addEventListener("change", e => {
current.value = parseInt(e.currentTarget.value)
rollDialog.render()
})
inputPtReve?.addEventListener("change", e => {
current.ptreve = parseInt(e.currentTarget.value)
rollDialog.render()
})
checkboxReserve?.addEventListener("change", e => {
current.isReserve = e.currentTarget.checked
rollDialog.render()
})
}
getExternalPartsFilter(partCode, rollData) {
if (this.visible(rollData)) {
const current = this.getCurrent(rollData)
switch (partCode) {
case PART_CARAC: return p => p.key == 'reve'
case PART_COMP: return p => current.draconics?.includes(p.label)
}
}
return undefined
}
}

View File

@@ -0,0 +1,16 @@
import { RdDCarac } from "../rdd-carac.js"
import { RollPartCheckbox } from "./roll-part-checkbox.mjs"
const SURENC = "surenc"
export class RollPartSurEnc extends RollPartCheckbox {
get code() { return SURENC }
visible(rollData) {
return RdDCarac.isActionPhysique(rollData.current.carac.key) && rollData.active.actor.isSurenc()
}
getCheckboxIcon(rollData) { return '<i class="fa-solid fa-weight-hanging"></i>' }
getCheckboxLabel(rollData) { return "Sur-enc." }
getCheckboxValue(rollData) { return rollData.active.actor.computeMalusSurEncombrement() }
}

View File

@@ -0,0 +1,67 @@
import { ITEM_TYPES } from "../constants.js"
import { Grammar } from "../grammar.js"
import { ROLL_MODE_TACHE } from "./roll-constants.mjs"
import { PART_CARAC } from "./roll-part-carac.mjs"
import { PART_COMP } from "./roll-part-comp.mjs"
import { RollPartSelect } from "./roll-part-select.mjs"
import { ROLLDIALOG_SECTION } from "./roll-part.mjs"
export const PART_TACHE = "tache"
export class RollPartTache extends RollPartSelect {
get code() { return PART_TACHE }
get section() { return ROLLDIALOG_SECTION.CHOIX }
isValid(rollData) { return rollData.active.actor.isPersonnage() }
visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_TACHE) }
loadRefs(rollData) {
const refs = this.getRefs(rollData)
refs.taches = rollData.active.actor.itemTypes[ITEM_TYPES.tache]
.filter(tache => tache.system.points_de_tache_courant < tache.system.points_de_tache)
.map(tache => RollPartTache.$extractTache(tache, rollData.active.actor))
if (refs.taches.length > 0) {
this.$selectTache(rollData)
}
}
choices(refs) { return refs.taches }
static $extractTache(tache, actor) {
return {
key: tache.id,
label: tache.name,
value: tache.system.difficulte,
tache: tache,
comp: actor.getCompetence(tache.system.competence)
}
}
$selectTache(rollData, key) {
this.selectByKey(rollData, key, 0)
}
async _onRender(rollDialog, context, options) {
const selectTache = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-tache"]`)
selectTache.addEventListener("change", e => {
const selectOptions = e.currentTarget.options
const index = selectOptions.selectedIndex
this.$selectTache(rollDialog.rollData, selectOptions[index]?.value)
rollDialog.setModeTitle()
rollDialog.render()
})
}
getExternalPartsFilter(partCode, rollData) {
if (this.visible(rollData)) {
const current = this.getCurrent(rollData)
switch (partCode) {
case PART_CARAC: return p => Grammar.equalsInsensitive(p.key, current?.tache.system.carac)
case PART_COMP: return p => p.label == current?.comp.name
}
}
return undefined
}
}

View File

@@ -0,0 +1,31 @@
import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"
const PART_TRICHER = "tricher"
export class RollPartTricher extends RollPart {
get code() { return PART_TRICHER }
get section() { return ROLLDIALOG_SECTION.CONDITIONS }
visible(rollData) { return game.user.isGM }
prepareContext(rollData) {
const current = this.getCurrent(rollData)
if (current.resultat == undefined) {
current.resultat = -1
}
}
getAjustements(rollData) {
rollData.current.resultat = this.getCurrent(rollData).resultat
return []
}
async _onRender(rollDialog, context, options) {
const input = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${this.code}"]`)
input?.addEventListener("change", e => {
this.getCurrent(rollDialog.rollData).resultat = parseInt(e.currentTarget.value)
})
}
}

97
module/roll/roll-part.mjs Normal file
View File

@@ -0,0 +1,97 @@
import { Misc } from "../misc.js"
export const ROLLDIALOG_SECTION = {
ACTION: 'action',
CARAC: 'carac',
COMP: 'comp',
CHOIX: 'choix',
CONDITIONS: 'conditions',
AJUSTEMENTS: 'ajustements',
}
export class RollPart {
static settingKey(rollPart, key) { return `roll-part-${rollPart.code}.${key}` }
get code() { throw new Error(`Pas dse code définie pour ${this}`) }
get name() { return this.code }
/** la section de la fenêtre ou le paramêtre apparaît */
get section() { return undefined }
get priority() { return 0 /* TODO */ }
/** le template handlebars pour affichage */
get template() { return `systems/foundryvtt-reve-de-dragon/templates/roll/roll-part-${this.code}.hbs` }
initialize(rollData) {
if (rollData.refs[this.code] == undefined) {
rollData.refs[this.code] = {}
}
if (rollData.current[this.code] == undefined) {
rollData.current[this.code] = {}
}
if (rollData.selected[this.code] == undefined) {
rollData.selected[this.code] = {}
}
}
/** le conteneur de données du RollPart */
getRefs(rollData) {
return rollData.refs[this.code]
}
/** les informations de sélection du paramètre */
getCurrent(rollData) {
return rollData.current[this.code]
}
setCurrent(rollData, current) {
rollData.current[this.code] = current
}
/** les informations minimales représentant la sélection dans le rollData permettant de restaurer la fenêtre */
getSaved(rollData) {
return rollData.selected[this.code] ?? {}
}
setSaved(rollData, saved) {
rollData.selected[this.code] = saved
}
restore(rollData) { }
store(rollData, targetData) { }
/**
* le texte à ajouter dans la barre de titre
* @returns une chaîne vide si rien ne doit être affiché
*/
title() { return '' }
isRollMode(rollData, mode) { return rollData.mode.current == mode }
isActive(rollData) { return this.isValid(rollData) && this.visible(rollData) }
isValid(rollData) { return true }
visible(rollData) { return true }
onReady() { }
loadRefs(rollData) { }
prepareContext(rollData) { }
/** ---- cross roll-part filtering ---- */
setFilter(rollData, filter) { }
getSpecialComp(rollData) { return [] }
setSpecialComp(comps) { }
getExternalPartsFilter(partCode, rollData) { return undefined }
setExternalFilter(visibleRollParts, rollData) {
const predicate = Misc.and(
visibleRollParts.map(p => p.getExternalPartsFilter(this.code, rollData)).filter(f => f != undefined)
)
this.setFilter(rollData, predicate);
}
toTemplateData() {
return { code: this.code, name: this.name, template: this.template, section: this.section }
}
getAjustements(rollData) {
return []
}
async _onRender(rollDialog, context, options) { }
}