Export de personnage en pdf
Première version d'export de personnage pdf
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
import { ACTOR_TYPES, ITEM_TYPES } from "../../constants.js"
|
||||
import { Grammar } from "../../grammar.js"
|
||||
import { RdDItemArme } from "../../item/arme.js"
|
||||
import { CATEGORIES_COMPETENCE_COMBAT } from "../../item/base-items.js"
|
||||
import { Misc } from "../../misc.js"
|
||||
import { RdDTimestamp } from "../../time/rdd-timestamp.js"
|
||||
import { PDFDocument } from "./pdf-lib/pdf-lib.esm.js"
|
||||
|
||||
|
||||
|
||||
const copyProperty = (actor, path) => foundry.utils.getProperty(actor, path)
|
||||
|
||||
// const findItem = (actor, itemType, itemName) => actor.itemTypes[itemType].find(it => Grammar.equalsInsensitive(formCompName(it.name), formCompName(itemName)))
|
||||
// const findItemPos = (actor, itemType, pos) => actor.itemTypes[itemType].length <= pos ? actor.itemTypes[itemType][pos] : length
|
||||
// const findProperty = (it, path) => it ? foundry.utils.getProperty(it, path) : undefined
|
||||
|
||||
// const findItemProperty = (actor, itemType, itemName, path) => findProperty(findItem(actor, itemType, itemName), path)
|
||||
// const findItemPosProperty = (actor, itemType, pos, path) => findProperty(findItemPos(actor, itemType, pos), path)
|
||||
|
||||
// const findArmeProperty = (actor, pos, path) => findProperty(findItemPos(actor, ITEM_TYPES.arme, pos), path)
|
||||
// const findSortProperty = (actor, pos, path) => findProperty(findItemPos(actor, ITEM_TYPES.sort, pos), path)
|
||||
|
||||
// const itemFormPath = (type, pos, property) => `${type}s.${pos}.${property}`
|
||||
|
||||
|
||||
const ACTOR_TO_FORM_MAPPING = [
|
||||
{ path: 'name' },
|
||||
{ path: 'system.carac.taille.value' },
|
||||
{ path: 'system.carac.apparence.value' },
|
||||
{ path: 'system.carac.apparence.xp' },
|
||||
{ path: 'system.carac.constitution.value' },
|
||||
{ path: 'system.carac.constitution.xp' },
|
||||
{ path: 'system.carac.force.value' },
|
||||
{ path: 'system.carac.force.value' },
|
||||
{ path: 'system.carac.force.xp' },
|
||||
{ path: 'system.carac.agilite.value' },
|
||||
{ path: 'system.carac.agilite.xp' },
|
||||
{ path: 'system.carac.dexterite.value' },
|
||||
{ path: 'system.carac.dexterite.xp' },
|
||||
{ path: 'system.carac.vue.value' },
|
||||
{ path: 'system.carac.vue.xp' },
|
||||
{ path: 'system.carac.ouie.value' },
|
||||
{ path: 'system.carac.ouie.xp' },
|
||||
{ path: 'system.carac.odoratgout.value' },
|
||||
{ path: 'system.carac.odoratgout.xp' },
|
||||
{ path: 'system.carac.volonte.value' },
|
||||
{ path: 'system.carac.volonte.xp' },
|
||||
{ path: 'system.carac.empathie.value' },
|
||||
{ path: 'system.carac.empathie.xp' },
|
||||
{ path: 'system.carac.intellect.value' },
|
||||
{ path: 'system.carac.intellect.xp' },
|
||||
{ path: 'system.carac.reve.value' },
|
||||
{ path: 'system.carac.reve.xp' },
|
||||
{ path: 'system.carac.chance.value' },
|
||||
{ path: 'system.carac.chance.xp' },
|
||||
|
||||
{ path: 'system.age' },
|
||||
{ path: 'system.sexe' },
|
||||
{ path: 'system.taille' },
|
||||
{ path: 'system.poids' },
|
||||
{ path: 'system.cheveux' },
|
||||
{ path: 'system.yeux' },
|
||||
{ path: 'system.beaute' },
|
||||
{ path: 'system.main' },
|
||||
{ path: 'system.heure' },
|
||||
{ path: 'computed.hn.heure', getter: actor => (RdDTimestamp.definition(actor.system.heure)?.heure ?? 0) + 1 },
|
||||
{ path: 'computed.hn.label', getter: actor => RdDTimestamp.definition(actor.system.heure)?.avecArticle },
|
||||
|
||||
{ path: 'system.carac.melee.value' },
|
||||
{ path: 'system.carac.tir.value' },
|
||||
{ path: 'system.carac.lancer.value' },
|
||||
{ path: 'system.carac.derobee.value' },
|
||||
{ path: 'system.sante.vie.value' },
|
||||
{ path: 'system.sante.endurance.value' },
|
||||
|
||||
{ path: 'system.attributs.sust.value' },
|
||||
{ path: 'system.attributs.sconst.value' },
|
||||
{ path: 'system.attributs.encombrement.value' },
|
||||
{ path: 'system.attributs.plusdom.value', getter: actor => Misc.toSignedString(actor.system.attributs.plusdom.value) },
|
||||
|
||||
// , getter: actor => actor.get
|
||||
]
|
||||
|
||||
export default class ExportPdf {
|
||||
|
||||
static init() {
|
||||
Hooks.on("getActorContextOptions", (actorDirectory, menus) => { ExportPdf.onActorDirectoryMenu(actorDirectory, menus) })
|
||||
}
|
||||
|
||||
static onActorDirectoryMenu(actorDirectory, menus) {
|
||||
menus.push({
|
||||
name: 'Export PDF',
|
||||
icon: '<i class="fa-regular fa-file-pdf"></i>',
|
||||
condition: target => actorDirectory.id == 'actors' && ExportPdf.$isActorPersonnage(this.$getActor(target)),
|
||||
callback: async target => await ExportPdf.exportActor(target)
|
||||
})
|
||||
}
|
||||
|
||||
static $getActor(target) {
|
||||
const entryId = $(target).closest(".directory-item")?.data("entryId")
|
||||
return game.actors.get(entryId)
|
||||
}
|
||||
|
||||
static $isActorPersonnage(actor) {
|
||||
return actor?.type == ACTOR_TYPES.personnage
|
||||
}
|
||||
|
||||
static async exportActor(target) {
|
||||
const actor = ExportPdf.$getActor(target)
|
||||
if (!ExportPdf.$isActorPersonnage(actor)) {
|
||||
ui.notifications.error("Pas de personnage sélectionné")
|
||||
return
|
||||
}
|
||||
const templatePdf = '/systems/foundryvtt-reve-de-dragon/assets/feuille-personnage.pdf';
|
||||
|
||||
const pdfBytes = await fetch(templatePdf).then(res => res.arrayBuffer())
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes)
|
||||
|
||||
const exporter = new ExportPdf(actor, pdfDoc)
|
||||
exporter.generateFeuillePersonnage()
|
||||
}
|
||||
|
||||
constructor(actor, pdfDoc) {
|
||||
this.pdfDoc = pdfDoc
|
||||
this.form = this.pdfDoc.getForm()
|
||||
this.actor = actor
|
||||
this.allComps = this.actor.itemTypes[ITEM_TYPES.competence]
|
||||
this.comps = this.allComps
|
||||
.filter(it => !it.isNiveauBase())
|
||||
.sort(Misc.ascending(it => it.name))
|
||||
|
||||
this.compsNonArmes = this.comps.filter(it => !ExportPdf.isCompCombat(it))
|
||||
this.compsArmes = this.comps.filter(it => ExportPdf.isCompCombat(it))
|
||||
this.addedComps = new Set([])
|
||||
}
|
||||
|
||||
static isCompCombat(comp) {
|
||||
return CATEGORIES_COMPETENCE_COMBAT.includes(comp.system.categorie) && !Grammar.includesLowerCaseNoAccent(comp.name, "corps à corps") && !Grammar.includesLowerCaseNoAccent(comp.name, "esquive")
|
||||
}
|
||||
|
||||
async generateFeuillePersonnage() {
|
||||
|
||||
this.$exportActorFields()
|
||||
this.$exportCompetences()
|
||||
this.$exportArchetype()
|
||||
this.$exportArmes()
|
||||
this.$exportSorts()
|
||||
|
||||
const pdfBytes = await this.pdfDoc.save();
|
||||
const filename = `rdd-${this.actor.name.slugify()}.pdf`;
|
||||
foundry.utils.saveDataToFile(pdfBytes, "application/pdf", filename);
|
||||
}
|
||||
|
||||
$exportActorFields() {
|
||||
ACTOR_TO_FORM_MAPPING.forEach(async (mapping) => {
|
||||
const path = mapping.path
|
||||
const value = mapping.getter ? mapping.getter(this.actor) : copyProperty(this.actor, path)
|
||||
this.$setFormValue(path, value)
|
||||
})
|
||||
}
|
||||
|
||||
$exportCompetences() {
|
||||
this.compsNonArmes
|
||||
.filter(it => !this.addedComps.has(it.id))
|
||||
.forEach(comp => {
|
||||
const formCompName = Grammar.toLowerCaseNoAccent(comp.name.replaceAll(/(\s|-|\')/g, '_'))
|
||||
this.$setFormCompetence(formCompName, comp)
|
||||
})
|
||||
const musique = this.compsNonArmes.filter(it => Grammar.includesLowerCaseNoAccent(it.name, 'musique'))
|
||||
.sort(Misc.descending(it => it.system.niveau))
|
||||
.filter(it => !this.addedComps.has(it.id))
|
||||
.find(it => true)
|
||||
if (musique) {
|
||||
this.$setFormCompetence('musique', musique)
|
||||
}
|
||||
}
|
||||
|
||||
$exportArchetype() {
|
||||
this.allComps.sort(Misc.ascending(it => it.system.niveau_archetype))
|
||||
.forEach(comp => {
|
||||
let formCompName = Grammar.toLowerCaseNoAccent(comp.name.replaceAll(/(\s|-|\')/g, '_'))
|
||||
if (formCompName.includes('musique')) {
|
||||
formCompName = 'musique'
|
||||
}
|
||||
this.$setFormValue(`competences.${formCompName}.niveau_archetype`, comp.system.niveau_archetype)
|
||||
})
|
||||
}
|
||||
|
||||
$setFormCompetenceArchetype(formCompName, comp, baseFormName = 'competences') {
|
||||
}
|
||||
|
||||
$exportArmes() {
|
||||
const uniques = new Set([])
|
||||
const armes = this.actor.itemTypes[ITEM_TYPES.arme].map(arme => arme.getTypeAttaques()
|
||||
.map(main => {
|
||||
const compName = arme.getCompetenceAction(main)
|
||||
const dommages = RdDItemArme.valeurMain(arme.system.dommages, main)
|
||||
const forceRequise = RdDItemArme.valeurMain(arme.system.force ?? 0, main)
|
||||
const comp = this.compsArmes.find(it => Grammar.equalsInsensitive(it.name, compName)) ?? this.actor.findItemLike(compName, ITEM_TYPES.competence)
|
||||
const unique = [comp.id, arme.name, dommages, forceRequise].join('|');
|
||||
if (uniques.has(unique)) {
|
||||
return undefined
|
||||
}
|
||||
uniques.add(unique)
|
||||
return { arme: arme, comp: comp, main: main }
|
||||
}))
|
||||
.reduce((a, b) => a.concat(b))
|
||||
.filter(it => it != undefined && !it.comp.isNiveauBase())
|
||||
.sort(Misc.descending(it => it.comp.niveau))
|
||||
|
||||
for (let pos = 0; pos < armes.length; pos++) {
|
||||
const it = armes[pos]
|
||||
this.$setFormArmeCompetence(pos, it.comp, it.arme, it.main, it.main)
|
||||
}
|
||||
|
||||
// TODO: list comps without weapons
|
||||
// TODO: list other comps not in the standard list -- use an instance of ExportPdf to hold state/built list
|
||||
const otherComps = this.comps.filter(it => !this.addedComps.has(it.id))
|
||||
for (let pos = 0; pos < otherComps.length; pos++) {
|
||||
const comp = otherComps[pos]
|
||||
this.$setFormCompetence(pos, comp, 'competences')
|
||||
this.$setFormValue(`competences.${pos}.name`, comp.name)
|
||||
}
|
||||
}
|
||||
|
||||
$setFormCompetence(formCompName, comp, baseFormName = 'competences') {
|
||||
|
||||
if (this.form.getFieldMaybe(`${baseFormName}.${formCompName}.niveau`)) {
|
||||
this.addedComps.add(comp.id)
|
||||
}
|
||||
if (comp.system.niveau != comp.system.base) {
|
||||
this.$setFormValue(`${baseFormName}.${formCompName}.niveau`, comp.system.niveau)
|
||||
}
|
||||
if (comp.system.xp > 0) {
|
||||
this.$setFormValue(`${baseFormName}.${formCompName}.xp`, comp.system.xp)
|
||||
}
|
||||
if (comp.system.xp_sort > 0) {
|
||||
this.$setFormValue(`${baseFormName}.${formCompName}.xp_sort`, comp.system.xp_sort)
|
||||
}
|
||||
if (CATEGORIES_COMPETENCE_COMBAT.includes(comp.system.categorie)) {
|
||||
this.$setFormValue(`${baseFormName}.${formCompName}.init`, comp.getBaseInit())
|
||||
}
|
||||
}
|
||||
|
||||
$setFormArmeCompetence(pos, comp, arme, main) {
|
||||
this.$setFormCompetence(pos, comp, 'armes')
|
||||
this.$setFormValue(`armes.${pos}.name`, arme.name)
|
||||
this.$setFormValue(`armes.${pos}.main`, main)
|
||||
this.$setFormValue(`armes.${pos}.plusdom`, RdDItemArme.valeurMain(arme.system.dommages, main))
|
||||
}
|
||||
|
||||
$exportSorts() {
|
||||
const sorts = this.actor.itemTypes[ITEM_TYPES.sort].sort(Misc.ascending(s => ExportPdf.$orderDraconic(s) + s.name))
|
||||
|
||||
for (let pos = 0; pos < sorts.length; pos++) {
|
||||
const sort = sorts[pos]
|
||||
this.$setFormSort(pos, sort)
|
||||
}
|
||||
}
|
||||
|
||||
$setFormSort(pos, sort) {
|
||||
this.$setFormValue(`sorts.${pos}.name`, sort.name)
|
||||
this.$setFormValue(`sorts.${pos}.voie`, sort.system.draconic)
|
||||
this.$setFormValue(`sorts.${pos}.tmr`, sort.system.caseTMRSpeciale ?? sort.system.caseTMR)
|
||||
this.$setFormValue(`sorts.${pos}.diff`, sort.system.difficulte)
|
||||
this.$setFormValue(`sorts.${pos}.reve`, sort.system.ptreve)
|
||||
this.$setFormValue(`sorts.${pos}.bonuscase`, sort.system.bonuscase)
|
||||
}
|
||||
|
||||
static $orderDraconic(s) {
|
||||
switch (s.system.draconic.substring(0, 1)) {
|
||||
case 'O': return 1
|
||||
case 'H': return 2
|
||||
case 'N': return 3
|
||||
case 'T': return 4
|
||||
}
|
||||
return 5
|
||||
}
|
||||
|
||||
$setFormValue(path, value) {
|
||||
const hasField = this.form.getFieldMaybe(path)
|
||||
if (hasField && value != undefined) {
|
||||
const field = this.form.getTextField(path)
|
||||
field.setText(value.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user