444 lines
16 KiB
JavaScript
444 lines
16 KiB
JavaScript
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) => {
|
|
show = Math.abs(show)
|
|
const start = base - show
|
|
return [...Array(2 * show + 1).keys()].map(it => start + it)
|
|
})
|
|
|
|
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 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;
|
|
rollData.current = rollData.current ?? {}
|
|
rollData.selected = rollData.selected ?? {}
|
|
rollData.mode = rollData.mode ?? {}
|
|
rollData.mode.retry = rollData.mode.retry ?? false
|
|
BASIC_PARTS.restore(rollData)
|
|
|
|
const loadedMode = ROLL_MODE_TABS.find(m => m.code == rollData.mode?.current)?.code
|
|
const allowedModes = ROLL_MODE_TABS.filter(m => m.isAllowed(rollData) && m.visible(rollData)).map(m => m.code)
|
|
|
|
rollData.mode.allowed = rollData.mode.retry ? [loadedMode] : rollData.mode.allowed ?? ROLL_MODE_TABS.map(m => m.code)
|
|
rollData.mode.current = allowedModes.find(m => m == rollData.mode?.current) ?? (allowedModes.length > 0 ? allowedModes[0] : 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.applyExternalImpacts(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(' '))
|
|
}
|
|
}
|