import { Grammar } from "../../grammar.js" import { RdDItemArme } from "../../item-arme.js" import { RdDItemCompetence } from "../../item-competence.js" import { RdDItemSort } from "../../item-sort.js" import { ITEM_TYPES } from "../../item.js" import { Misc } from "../../misc.js" import { RdDTimestamp } from "../../time/rdd-timestamp.js" import { RdDBonus } from "../../rdd-bonus.js" import { TMRType } from "../../tmr-utility.js" export const CATEGORIES_COMPETENCES = [ "generale", "particuliere", "specialisee", "connaissance", ] export const CATEGORIES_DRACONIC = [ "draconic", ] const CATEGORIES_COMBAT = [ "melee", "tir", "lancer" ] const NIVEAU_BASE = { "generale": -4, "particuliere": -8, "specialisee": -11, "connaissance": -11, "draconic": -11, "melee": -6, "tir": -8, "lancer": -8, } class ColumnMappingFactory { static createMappingArme(part, i) { return { column: `arme_${part}_${i}`, getter: (actor, context) => Mapping.getArme(actor, context, part, i) } } static createMappingSort(part, i) { return { column: `sort_${part}_${i}`, getter: (actor, context) => Mapping.getSort(actor, context, part, i) } } } const NB_ARMES = 10 const NB_SORTS = 20 const TABLEAU_ARMES = [...Array(NB_ARMES).keys()] const TABLEAU_SORTS = [...Array(NB_SORTS).keys()] const MAPPING_BASE = [ { column: "ID", colName: 'ID', getter: (actor, context) => actor.id }, { column: "name", getter: (actor, context) => actor.name }, { column: "metier", colName: 'Métier', getter: (actor, context) => actor.system.metier }, { column: "biographie", colName: 'Biographie', getter: (actor, context) => actor.system.biographie }, { column: "taille", getter: (actor, context) => actor.system.carac.taille.value }, { column: "apparence", rollClass: 'roll-carac', getter: (actor, context) => actor.system.carac.apparence.value }, { column: "constitution", rollClass: 'roll-carac', getter: (actor, context) => actor.system.carac.constitution.value }, { column: "force", rollClass: 'roll-carac', getter: (actor, context) => actor.system.carac.force.value }, { column: "agilite", rollClass: 'roll-carac', colName: 'Agilité', getter: (actor, context) => actor.system.carac.agilite.value }, { column: "dexterite", rollClass: 'roll-carac', colName: 'Dextérité', getter: (actor, context) => actor.system.carac.dexterite.value }, { column: "vue", rollClass: 'roll-carac', getter: (actor, context) => actor.system.carac.vue.value }, { column: "ouie", rollClass: 'roll-carac', colName: 'Ouïe', getter: (actor, context) => actor.system.carac.ouie.value }, { column: "odoratgout", rollClass: 'roll-carac', colName: 'Odo-goût', getter: (actor, context) => actor.system.carac.odoratgout.value }, { column: "volonte", rollClass: 'roll-carac', colName: 'Volonté', getter: (actor, context) => actor.system.carac.volonte.value }, { column: "intellect", rollClass: 'roll-carac', getter: (actor, context) => actor.system.carac.intellect.value }, { column: "empathie", rollClass: 'roll-carac', getter: (actor, context) => actor.system.carac.empathie.value }, { column: "reve", rollClass: 'roll-carac', colName: 'Rêve', getter: (actor, context) => actor.system.carac.reve.value }, { column: "chance", rollClass: 'roll-carac', getter: (actor, context) => actor.system.carac.chance.value }, { column: "melee", rollClass: 'roll-carac', colName: 'Mêlée', getter: (actor, context) => actor.system.carac.melee.value }, { column: "tir", rollClass: 'roll-carac', getter: (actor, context) => actor.system.carac.tir.value }, { column: "lancer", rollClass: 'roll-carac', getter: (actor, context) => actor.system.carac.lancer.value }, { column: "derobee", rollClass: 'roll-carac', colName: 'Dérobée', getter: (actor, context) => actor.system.carac.derobee.value }, { column: "vie", getter: (actor, context) => actor.system.sante.vie.max }, { column: "endurance", getter: (actor, context) => actor.system.sante.endurance.max }, { column: "plusdom", colName: '+dom', getter: (actor, context) => actor.system.attributs.plusdom.value }, { column: "protectionnaturelle", colName: 'Protection naturelle', getter: (actor, context) => actor.system.attributs.protection.value > 0 ? actor.system.attributs.protection.value : '' }, { column: "description", getter: (actor, context) => Mapping.getDescription(actor) }, { column: "armure", getter: (actor, context) => Mapping.getArmure(actor, context) }, { column: "protectionarmure", colName: 'Protection', getter: (actor, context) => Mapping.getProtectionArmure(actor, context) }, { column: "malus_armure", getter: (actor, context) => Mapping.getMalusArmure(actor, context) }, { column: "reve_actuel", rollClass: 'roll-reve-actuel', colName: 'Rêve actuel', getter: (actor, context) => actor.system.reve.reve.value }, { column: "vie_actuel", rollClass: 'jet-vie', getter: (actor, context) => actor.system.sante.vie.value }, { column: "endurance_actuel", rollClass: 'jet-endurance', getter: (actor, context) => actor.system.sante.endurance.value }, { column: "esquive", getter: (actor, context) => Mapping.getEsquive(context) }, { column: "esquive_armure", getter: (actor, context) => Mapping.getEsquiveArmure(context) }, { column: "competences", getter: (actor, context) => Mapping.getCompetences(actor, CATEGORIES_COMPETENCES) }, { column: "draconic", getter: (actor, context) => Mapping.getCompetences(actor, CATEGORIES_DRACONIC) }, ] const MAPPING_ARMES = TABLEAU_ARMES.map(i => ColumnMappingFactory.createMappingArme('name', i)) .concat(TABLEAU_ARMES.map(i => ColumnMappingFactory.createMappingArme('niveau', i))) .concat(TABLEAU_ARMES.map(i => ColumnMappingFactory.createMappingArme('init', i))) .concat(TABLEAU_ARMES.map(i => ColumnMappingFactory.createMappingArme('dommages', i))) const MAPPING_SORTS = TABLEAU_SORTS.map(i => ColumnMappingFactory.createMappingSort('voie', i)) .concat(TABLEAU_SORTS.map(i => ColumnMappingFactory.createMappingSort('description', i))) .concat(TABLEAU_SORTS.map(i => ColumnMappingFactory.createMappingSort('bonus', i))) const MAPPING = MAPPING_BASE .concat(MAPPING_ARMES) .concat(MAPPING_SORTS) export class Mapping { static getMapping() { return MAPPING } static getColumns() { return MAPPING.map(it => it.column) } static getValues(actor) { const context = Mapping.prepareContext(actor) return MAPPING.map(it => it.getter(actor, context)) } static getAsObject(actor) { const context = Mapping.prepareContext(actor) return Object.fromEntries(MAPPING.map(it => [it.column, { colName: it.colName ?? it.column, value: it.getter(actor, context) }])) } static getValues(actor) { const context = Mapping.prepareContext(actor) return MAPPING.map(it => it.getter(actor, context)) } static prepareContext(actor) { return { armes: Mapping.prepareArmes(actor), armure: Mapping.prepareArmure(actor), esquive: Mapping.prepareEsquive(actor), sorts: Mapping.prepareSorts(actor) } } static prepareArmes(actor) { const armes = actor.items.filter(it => it.type == ITEM_TYPES.arme) RdDItemArme.ajoutCorpsACorps(armes, actor) return armes.map(arme => [ arme.system.unemain ? Mapping.prepareArme(actor, arme, 'unemain') : undefined, arme.system.deuxmains ? Mapping.prepareArme(actor, arme, 'deuxmains') : undefined, !(arme.system.unemain || arme.system.deuxmains) ? Mapping.prepareArme(actor, arme, 'competence') : undefined, arme.system.lancer != "" ? Mapping.prepareArme(actor, arme, 'lancer') : undefined, arme.system.tir != "" ? Mapping.prepareArme(actor, arme, 'tir') : undefined] .filter(it => it != undefined)) .reduce((a, b) => a.concat(b), []) } static prepareArme(actor, arme, maniement) { const nameCompArme = RdDItemArme.getCompetenceArme(arme, maniement) const competence = actor.getCompetence(nameCompArme) if (RdDItemCompetence.isNiveauBase(competence)) { return undefined } const categorie = Mapping.complementCategorie(arme, maniement) const dommages = Mapping.dommagesArme(actor, arme, maniement) return { name: arme.name + categorie, niveau: Misc.toSignedString(competence.system.niveau), init: Mapping.calculBaseInit(actor, competence.system.categorie) + competence.system.niveau, dommages: dommages, competence: competence, arme: arme } } static dommagesArme(actor, arme, maniement){ const dmgArme = RdDItemArme.dommagesReels(arme, maniement) const dommages = Misc.toSignedString(dmgArme + RdDBonus.bonusDmg(actor, maniement, dmgArme)) switch(arme.system.mortalite) { case 'non-mortel': return `(${dommages})` case 'empoignade': return '-' } return dommages } static complementCategorie(arme, maniement) { switch (maniement) { case 'unemain': return (arme.system.deuxmains) ? ' 1 main' : (arme.system.lancer || arme.system.tir) ? ' mêlée' : '' case 'deuxmains': return (arme.system.unemain) ? ' 2 mains' : (arme.system.lancer || arme.system.tir) ? ' mêlée' : '' case 'lancer': return (arme.system.unemain || arme.system.deuxmains || arme.system.tir) ? ' jet' : '' case 'tir': return (arme.system.unemain || arme.system.deuxmains || arme.system.lancer) ? ' tir' : '' } return '' } static calculBaseInit(actor, categorie) { const mapping = MAPPING_BASE.find(it => it.column == categorie) if (mapping) { switch (categorie) { case 'melee': case 'tir': case 'lancer': const caracteristique = Number(actor.system.carac[categorie].value) return Math.floor(caracteristique / 2) } } return 0 } static prepareArmure(actor) { const armures = actor.itemTypes[ITEM_TYPES.armure].filter(it => it.system.equipe) if (armures.length > 1) { console.warn(`${actor.name} a équipé ${armures.length} armures, seule la première sera considérée`) } if (armures.length > 0) { const armure = armures[0] return { name: armure.name, protection: armure.system.protection, malus: armure.system.malus ?? 0 } } return { name: '', protection: actor.system.attributs.protection.value, malus: 0 } } static prepareEsquive(actor) { const esquives = actor.getCompetences("Esquive") if (esquives.length > 0) { const esquive = esquives[0] return { name: esquive.name, niveau: esquive.system.niveau, competence: esquive } } return undefined } static prepareSorts(actor) { const codeVoies = Mapping.getCompetencesCategorie(actor, CATEGORIES_DRACONIC) .map(it => RdDItemSort.getVoieCode(it)) return actor.itemTypes[ITEM_TYPES.sort].map(it => Mapping.prepareSort(it, codeVoies)) .sort(Misc.ascending(it => `${it.voie} : ${it.description}`)) } static prepareSort(sort, voies) { return { voie: RdDItemSort.getCodeDraconic(sort, voies), description: Mapping.descriptionSort(sort), bonus: Mapping.bonusCase(sort) } } static descriptionSort(sort) { const ptSeuil = Array(sort.system.coutseuil).map(it => '*') const caseTMR = sort.system.caseTMRspeciale.length > 0 ? Mapping.toVar(sort.system.caseTMRspeciale) : Misc.upperFirst(TMRType[sort.system.caseTMR].name) const ptreve = Mapping.addSpaceToNonNumeric(sort.system.ptreve) const diff = Mapping.addSpaceToNonNumeric(sort.system.difficulte) return `${sort.name}${ptSeuil} (${caseTMR}) R${diff} r${ptreve}` } static addSpaceToNonNumeric(value) { return Number.isNumeric(value) ? value : ' ' + Mapping.toVar(value) } static toVar(value) { return value.replace('variable', 'var') } static bonusCase(sort) { const list = RdDItemSort.bonuscaseStringToList(sort.system.bonuscase).sort(Misc.descending(it => it.bonus)) if (list.length > 0) { const bonus = list[0] return `+${bonus.bonus}% en ${bonus.case}` } return '' } static getDescription(actor) { const sexe = actor.system.sexe const sexeFeminin = sexe.length > 0 && sexe.charAt(0).toLowerCase() == 'f' ? 'Née' : 'Né' const race = ['', 'humain'].includes(Grammar.toLowerCaseNoAccent(actor.system.race)) ? '' : (actor.system.race + ' ') const heure = actor.system.heure const hn = `${sexeFeminin} à l'heure ${RdDTimestamp.definition(heure).avecArticle}` const age = actor.system.age ? `${actor.system.age} ans` : undefined const taille = actor.system.taille const poids = actor.system.poids const cheveux = actor.system.cheveux ? `cheveux ${actor.system.cheveux}` : undefined const yeux = actor.system.yeux ? `yeux ${actor.system.yeux}` : undefined const beaute = actor.system.beaute ? `beauté ${actor.system.beaute}` : undefined const list = [race, hn, age, taille, poids, cheveux, yeux, beaute] return Misc.join(list.filter(it => it), ', ') } static getArmure(actor, context) { return context.armure?.name ?? '' } static getProtectionArmure(actor, context) { const naturelle = Number(actor.system.attributs.protection.value) if (context?.armure?.protection == undefined) { return naturelle } if (Number.isNumeric(context?.armure?.protection)) { return Number(context?.armure?.protection ?? 0) + naturelle } return context?.armure.protection + (naturelle > 0 ? `+${naturelle}` : '') } static getMalusArmure(actor, context) { return context?.armure?.malus ?? 0 } static getEsquive(context) { if (context.esquive) { return Misc.toSignedString(context.esquive.niveau) } return '' } static getEsquiveArmure(context) { if (context.esquive) { const niveau = context.esquive.niveau + context.armure.malus return Misc.toSignedString(niveau) } return '' } static getCompetences(actor, categories) { const competences = Mapping.getCompetencesCategorie(actor, categories) if (competences.length == 0) { return '' } const byCategories = Mapping.competencesByCategoriesByNiveau(competences, categories) const txtByCategories = Object.values(byCategories) .map(it => it.competencesParNiveau) .map(byNiveau => { const niveaux = Object.keys(byNiveau) .map(it => Number(it)).sort(Misc.ascending()) if (niveaux.length == 0) { return '' } const txtCategorieByNiveau = niveaux.map(niveau => { const names = Misc.join(byNiveau[niveau].map(it => it.name).sort(Misc.ascending()), ', ') return names + ' ' + Misc.toSignedString(niveau) }) const txtCategorie = Misc.join(txtCategorieByNiveau, ' / ') return txtCategorie }).filter(it => it != '') return Misc.join(txtByCategories, ' / ') } static competencesByCategoriesByNiveau(competences, categories) { return categories.map(c => { return { categorie: c, competencesParNiveau: Misc.classify( competences.filter(comp => comp.system.categorie == c), comp => comp.system.niveau) } }) } static getArme(actor, context, part, numero) { if (numero < context.armes.length) { return context.armes[numero][part] ?? '' } return '' } static getCompetencesCategorie(actor, categories) { return actor.itemTypes[ITEM_TYPES.competence] .filter(it => categories.includes(it.system.categorie)) .filter(it => !RdDItemCompetence.isNiveauBase(it)) } static getSort(actor, context, part, numero) { if (numero < context.sorts.length) { return context.sorts[numero][part] } return '' } }