Export de personnage en pdf

Première version d'export de personnage pdf
This commit is contained in:
2025-11-01 00:54:57 +01:00
parent 05c7a91f93
commit 9b91850731
20 changed files with 79502 additions and 40 deletions

View File

@@ -190,7 +190,7 @@ export class RdDActor extends RdDBaseActorSang {
const forceRequise = RdDItemArme.valeurMain(arme.system.force ?? 0, main)
const ecaillesEfficacite = arme.system.magique ? arme.system.ecaille_efficacite : 0;
const comp = this.getCompetence(RdDActor.$getCompetenceAction(arme, main))
const comp = this.getCompetence(arme.getCompetenceAction(main))
const unique = [comp.id, arme.name, dommages, forceRequise, ecaillesEfficacite].join('|');
if (uniques.includes(unique)) {
return
@@ -219,15 +219,12 @@ export class RdDActor extends RdDBaseActorSang {
}
addAttaque(RdDItemArme.empoignade(this), ATTAQUE_TYPE.CORPS_A_CORPS)
this.itemTypes[ITEM_TYPES.arme]
.filter(it => it.isAttaque())
.sort(Misc.ascending(it => it.name))
.forEach(arme => {
if (arme.system.unemain && arme.system.competence && arme.system.resistance > 0) { addAttaque(arme, ATTAQUE_TYPE.UNE_MAIN) }
if (arme.system.deuxmains && arme.system.competence && arme.system.resistance > 0) { addAttaque(arme, ATTAQUE_TYPE.DEUX_MAINS) }
if (arme.system.lancer && arme.system.resistance > 0) { addAttaque(arme, ATTAQUE_TYPE.LANCER) }
if (arme.system.tir) { addAttaque(arme, ATTAQUE_TYPE.TIR) }
})
.forEach(arme => arme.getTypeAttaques().forEach(t => addAttaque(arme, t)))
addAttaque(RdDItemArme.pugilat(this), ATTAQUE_TYPE.CORPS_A_CORPS)
return actions
@@ -244,16 +241,6 @@ export class RdDActor extends RdDBaseActorSang {
}
}
static $getCompetenceAction(arme, main) {
switch (main) {
case ATTAQUE_TYPE.UNE_MAIN: return arme.competence1Mains()
case ATTAQUE_TYPE.DEUX_MAINS: return arme.competence2Mains()
case ATTAQUE_TYPE.LANCER: return arme.system.lancer
case ATTAQUE_TYPE.TIR: return arme.system.tir
default: return arme.system.competence
}
}
/* -------------------------------------------- */
async $perteReveEnchantementsChateauDormants() {
const toUpdate = this.items.filter(it => [ITEM_TYPES.potion, ITEM_TYPES.gemme].includes(it.type))

View File

@@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -20,11 +20,11 @@ export class RdDInitiative {
return "1d6" + (base >= 0 ? "+" : "") + base
}
static ajustementInitiative(caracValue, niveau, bonus) {
static ajustementInitiative(caracValue, niveau, bonus = 0) {
return niveau + Math.floor(caracValue / 2) + bonus
}
static formule(phase, carac, niveau, bonusMalus) {
static formule(phase, carac, niveau, bonusMalus = 0) {
const ajustement = RdDInitiative.ajustementInitiative(carac, niveau, bonusMalus)
return { phase, ajustement }
}

View File

@@ -1,7 +1,8 @@
import { Grammar } from "./grammar.js";
import { RdDInitiative } from "./initiative.mjs";
import { RdDItem } from "./item.js";
import { CATEGORIES_COMPETENCES, SANS_COMPETENCE } from "./item/base-items.js";
import { CATEGORIES_COMPETENCE_COMBAT, CATEGORIES_COMPETENCES, SANS_COMPETENCE } from "./item/base-items.js";
import { Misc } from "./misc.js";
const competenceTroncs = [["Esquive", "Dague", "Corps à corps"],
@@ -46,6 +47,30 @@ export class RdDItemCompetence extends RdDItem {
static get defaultIcon() { return "systems/foundryvtt-reve-de-dragon/icons/competence_defaut.webp" }
isNiveauBase() {
return this.system.niveau == this.system.base && this.system.xp == 0
}
getBaseInit() {
const carac = this.getInitCarac()
if (carac == undefined) {
return undefined
}
return RdDInitiative.ajustementInitiative(carac.value, this.system.niveau)
}
getInitCarac() {
if (!this.actor) {
return undefined
}
switch (this.system.categorie) {
case CATEGORIES_COMPETENCES.melee.key: return this.actor.system.carac.melee
case CATEGORIES_COMPETENCES.tir.key: return this.actor.system.carac.tir
case CATEGORIES_COMPETENCES.lancer.key: return this.actor.system.carac.lancer
case CATEGORIES_COMPETENCES.draconic.key: return this.actor.system.carac.reve
}
return undefined
}
/* -------------------------------------------- */
static getLabelCategorie(category) {
return CATEGORIES_COMPETENCES[category].label;

View File

@@ -45,6 +45,16 @@ export class RdDItemArme extends RdDItem {
isParade() { return this.system.resistance > 0 && this.system.categorie_parade }
isBouclier() { return RdDItemArme.getCategorieParade(this).includes('bouclier') }
getCompetenceAction(main) {
switch (main) {
case ATTAQUE_TYPE.UNE_MAIN: return this.competence1Mains()
case ATTAQUE_TYPE.DEUX_MAINS: return this.competence2Mains()
case ATTAQUE_TYPE.LANCER: return this.system.lancer
case ATTAQUE_TYPE.TIR: return this.system.tir
default: return this.system.competence
}
}
/* -------------------------------------------- */
static valeurMain(valeurs, main) {
valeurs = valeurs?.toString() ?? ""
@@ -78,17 +88,26 @@ export class RdDItemArme extends RdDItem {
return arme.name
case ITEM_TYPES.arme:
switch (maniement) {
case ATTAQUE_TYPE.COMPETENCE: return arme.system.competence;
case ATTAQUE_TYPE.COMPETENCE: return arme.system.competence
case ATTAQUE_TYPE.UNE_MAIN: return arme.competence1Mains()
case ATTAQUE_TYPE.DEUX_MAINS: return arme.competence2Mains()
case ATTAQUE_TYPE.TIR: case 'tir': return arme.system.tir
case ATTAQUE_TYPE.LANCER: case 'lancer': return arme.system.lancer;
case ATTAQUE_TYPE.LANCER: case 'lancer': return arme.system.lancer
case ATTAQUE_TYPE.CORPS_A_CORPS: return CORPS_A_CORPS
}
}
return undefined
}
getTypeAttaques() {
return [
...(this.system.unemain && this.system.competence && this.system.resistance > 0) ? [ATTAQUE_TYPE.UNE_MAIN] : [],
...(this.system.deuxmains && this.system.competence && this.system.resistance > 0) ? [ATTAQUE_TYPE.DEUX_MAINS] : [],
...(this.system.lancer && this.system.resistance > 0) ? [ATTAQUE_TYPE.LANCER] : [],
...(this.system.tir) ? [ATTAQUE_TYPE.TIR] : [],
]
}
static computeNiveauArmes(armes, competences) {
for (const arme of armes) {
arme.system.niveau = RdDItemArme.niveauCompetenceArme(arme, competences);

View File

@@ -14,27 +14,28 @@ export const SANS_COMPETENCE = {
description: "",
descriptionmj: "",
defaut_carac: "",
},
},
img: "systems/foundryvtt-reve-de-dragon/icons/templates/icone_parchement_vierge.webp"
}
}
export const CATEGORIES_COMPETENCES = {
"generale": { base: -4, label: "Générales" },
"particuliere": { base: -8, label: "Particulières" },
"specialisee": { base: -11, label: "Spécialisées" },
"connaissance": { base: -11, label: "Connaissances" },
"draconic": { base: -11, label: "Draconic" },
"melee": { base: -6, label: "Mêlée" },
"tir": { base: -8, label: "Tir" },
"lancer": { base: -8, label: "Lancer" }
generale: { key: 'generale', base: -4, label: "Générales" },
particuliere: { key: 'particuliere', base: -8, label: "Particulières" },
specialisee: { key: 'specialisee', base: -11, label: "Spécialisées" },
connaissance: { key: 'connaissance', base: -11, label: "Connaissances" },
draconic: { key: 'draconic', base: -11, label: "Draconic" },
melee: { key: 'melee', base: -6, label: "Mêlée" },
tir: { key: 'tir', base: -8, label: "Tir" },
lancer: { key: 'lancer', base: -8, label: "Lancer" }
}
export const CATEGORIES_COMPETENCE_COMBAT = [CATEGORIES_COMPETENCES.melee, CATEGORIES_COMPETENCES.tir, CATEGORIES_COMPETENCES.lancer].map(it => it.key)
export const CATEGORIES_COMPETENCES_CREATURES = {
"generale": { base: 0, label: "Générale" },
"naturelle": { base: 0, label: "Arme naturelle" },
"melee": { base: 0, label: "Mêlée" },
"parade": { base: 0, label: "Parade" },
"tir": { base: 0, label: "Tir" },
"lancer": { base: 0, label: "Lancer" },
"possession": { base: 0, label: "Possession" },
generale: { key: 'generale', base: 0, label: "Générale" },
naturelle: { key: 'naturelle', base: 0, label: "Arme naturelle" },
melee: { key: 'melee', base: 0, label: "Mêlée" },
parade: { key: 'parade', base: 0, label: "Parade" },
tir: { key: 'tir', base: 0, label: "Tir" },
lancer: { key: 'lancer', base: 0, label: "Lancer" },
possession: { key: 'possession', base: 0, label: "Possession" },
}

View File

@@ -89,6 +89,7 @@ import { Migrations } from './migrations.js'
import RollDialog from "./roll/roll-dialog.mjs"
import ChatRollResult from "./roll/chat-roll-result.mjs"
import ExportPdf from "./actor/export-pdf/export-pdf.mjs"
/**
* RdD system
@@ -296,6 +297,7 @@ export class SystemReveDeDragon {
RdDPossession.init()
TMRRencontres.init()
ExportScriptarium.init()
ExportPdf.init()
RollDialog.init()
ChatRollResult.init()
}