/************************************************************************************/ import "./xregexp-all.js"; import { SystemCompendiums } from "../settings/system-compendiums.js"; import { RdDBaseActorReve } from "../actor/base-actor-reve.js"; import { Grammar } from "../grammar.js"; import { Misc } from "../misc.js"; import { ENTITE_INCARNE, ENTITE_NONINCARNE } from "../constants.js"; const WHITESPACES = "\\s+" const NUMERIC = "[\\+\\-]?\\d+" const NUMERIC_VALUE = "(?" + NUMERIC + ")" const XREGEXP_NAME = "(?[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+)" const XREGEXP_COMP_CREATURE = WHITESPACES + "(?\\d+)" + WHITESPACES + NUMERIC_VALUE + "(" + WHITESPACES + "(?\\d+)?\\s+?(?[\\+\\-]?\\d+)?" + ")?" // Skill parser depending on the type of actor const compParser = { personnage: "(\\D+)*" + WHITESPACES + NUMERIC_VALUE, creature: XREGEXP_COMP_CREATURE, entite: XREGEXP_COMP_CREATURE } const XREGEXP_SORT_VOIE = "[OHNT\\/]+" const XREGEXP_SORT_CASE = "\\((?[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+)\\)"; const XREGEXP_SORT = "(" + XREGEXP_SORT_VOIE + WHITESPACES + XREGEXP_NAME + WHITESPACES + XREGEXP_SORT_CASE + WHITESPACES + "R(?([\\-\\d]+|(\\w|\\s)+))" + WHITESPACES + "r(?(\\d+(\\+)?|\\s\\w+))" + "(" + WHITESPACES + "\\+(?\\d+)\\s?%" + WHITESPACES + "en" + WHITESPACES + "(?[A-M]\\d{1,2})" + ")?" + ")" // Main class for parsing a stat block export class RdDStatBlockParser { static openInputDialog() { let dialog = new Dialog({ title: "Import de stats de PNJ/Créatures", content: `

Coller le texte de la stat ici

`, buttons: { ok: { label: "OK", callback: async (html) => { let statBlock = html.find("#statBlock")[0].value; await RdDStatBlockParser.parseStatBlock(statBlock); dialog.close(); } }, cancel: { label: "Cancel" } } }); dialog.render(true); } static fixWeirdPDF(statString) { // Split the statString into lines let lines = statString.split("\n"); let newLines = []; let index = 0; let nextType = "string"; // Loop through each line for (let i = 0; i < lines.length; i++) { // remove trailing spaces lines[i] = lines[i].trim(); // Is it text ? if (lines[i].match(/^[a-zA-Zéêè\s]+/)) { if (nextType == "string") { newLines[index] = lines[i]; nextType = "number"; } else { console.log("Wrong sequence string detected...", lines[i], nextType); } } // Is it a number ? if (lines[i].match(/^[\d\s]+/)) { if (nextType == "number") { newLines[index] = newLines[index] + lines[i]; nextType = "string"; index++; } else { console.log("Wrong sequence number detected...", lines[i], nextType); } } } } static getHeureKey(heure) { for (let h of game.system.rdd.config.heuresRdD) { if (h.label.toLowerCase() == heure.toLowerCase()) { return h.value; } } return "vaisseau"; } static async parseStatBlock(statString, type = "npc") { //statString = statBlock03; if (!statString) { return; } // Special function to fix strange/weird copy/paste from PDF readers // Unused up to now : this.fixWeirdPDF(statString); // Replace all endline by space in the statString statString = statString.replace(/\n/g, " "); // Remove all multiple spaces statString = statString.replace(/\s{2,}/g, " "); // Remove all leading and trailing spaces statString = statString.trim(); // TODO: check for entite let actorType = RdDStatBlockParser.parseActorType(statString); // Now start carac let actorData = foundry.utils.deepClone(game.model.Actor[actorType]); for (let key in actorData.carac) { let caracDef = actorData.carac[key]; // Parse the stat string for each caracteristic let carac = XRegExp.exec(statString, XRegExp(caracDef.label + "\\s+(?\\d+)", 'giu')); if (carac?.value) { actorData.carac[key].value = Number(carac.value); } } // If creature we need to setup additionnal fields switch (actorType) { case "creature": RdDStatBlockParser.parseCreature(statString, actorData) break case "entite": RdDStatBlockParser.parseEntite(statString, actorData) break } let items = []; // Get skills from compendium const competences = await SystemCompendiums.getCompetences(actorType); //console.log("Competences : ", competences); for (let comp of competences) { let compMatch = XRegExp.exec(statString, XRegExp(comp.name + compParser[actorType], 'giu')); if (compMatch) { comp = comp.toObject() comp.system.niveau = Number(compMatch.value); if (actorType == "creature" || actorType == "entite") { comp.system.carac_value = Number(compMatch.carac); if (compMatch.dommages != undefined) { comp.system.dommages = Number(compMatch.dommages); comp.system.iscombat = true; } } items.push(comp) } else if (actorType == "personnage") { comp = comp.toObject() items.push(comp) } } // Now process weapons const weapons = await SystemCompendiums.getWorldOrCompendiumItems("arme", "equipement") //console.log("Equipement : ", equipment); // TODO: les noms d'armes peuvent avoir un suffixe (à une main, lancée) qui détermine la compétence correspondante // TODO: une arme peut être spécifique ("fourche"), ajouter une compétence dans ces cas là? for (let weapon of weapons) { let weapMatch = XRegExp.exec(statString, XRegExp(weapon.name + "\\s+(?\\+\\d+)", 'giu')); if (weapMatch) { weapon = weapon.toObject() weapon.system.equipe = 'true' items.push(weapon) // now process the skill if (weapon.system?.competence != "") { let wComp = items.find(i => Grammar.equalsInsensitive(i.name, weapon.system.competence)) if (wComp) { wComp.system.niveau = Number(weapMatch.value); } } if (weapon.system?.tir != "") { let wComp = items.find(i => Grammar.equalsInsensitive(i.name, weapon.system.tir)) if (wComp) { wComp.system.niveau = Number(weapMatch.value); } } if (weapon.system?.lancer != "") { let wComp = items.find(i => Grammar.equalsInsensitive(i.name, weapon.system.lancer)) if (wComp) { wComp.system.niveau = Number(weapMatch.value); } } } } // Now process armors const armors = await SystemCompendiums.getWorldOrCompendiumItems("armure", "equipement") for (let armor of armors) { let matchArmor = XRegExp.exec(statString, XRegExp(armor.name, 'giu')); if (matchArmor) { armor = armor.toObject() armor.system.equipe = true items.push(armor); } } // Attemp to detect spell let hautRevant = false let sorts = await SystemCompendiums.getWorldOrCompendiumItems("sort", "sorts-oniros") sorts = sorts.concat(await SystemCompendiums.getWorldOrCompendiumItems("sort", "sorts-hypnos")) sorts = sorts.concat(await SystemCompendiums.getWorldOrCompendiumItems("sort", "sorts-narcos")) sorts = sorts.concat(await SystemCompendiums.getWorldOrCompendiumItems("sort", "sorts-thanatos")) XRegExp.forEach(statString, XRegExp(XREGEXP_SORT, 'giu'), function (matchSort, i) { let sort = sorts.find(s => Grammar.equalsInsensitive(s.name, matchSort.name)) if (sort) { hautRevant = true sort = sort.toObject(); if (matchSort.bonus && matchSort.bonuscase) { sort.system.bonuscase = `${matchSort.bonuscase}:${matchSort.bonus}` } items.push(sort); } }); if (hautRevant) { let tetes = await SystemCompendiums.getWorldOrCompendiumItems("tete", "tetes-de-dragon-pour-tous-personnages") let donHR = tetes.find(t => Grammar.equalsInsensitive(t.name, "Don de Haut-Rêve")) if (donHR) { items.push(donHR.toObject()); } } if (actorType == "personnage") { let feminin = XRegExp.exec(statString, XRegExp("né(?e?) à", 'giu')); actorData.sexe = (feminin?.value == 'e') ? 'féminin' : 'masculin' // Get hour name : heure du XXXXX let heure = XRegExp.exec(statString, XRegExp("heure (du|de la|des|de l\')\\s*(?[A-Za-zÀ-ÖØ-öø-ÿ\\s]+),", 'giu')); actorData.heure = this.getHeureKey(heure?.value || "Vaisseau"); // Get age let age = XRegExp.exec(statString, XRegExp("(?\\d+) ans", 'giu')); if (age?.value) { actorData.age = Number(age.value); } // Get height let taille = XRegExp.exec(statString, XRegExp("(?\\d+m\\d+)", 'giu')); if (taille?.value) { actorData.taille = taille.value; } // Get weight let poids = XRegExp.exec(statString, XRegExp("(?\\d+) kg", 'giu')); if (poids?.value) { actorData.poids = poids.value; } // Get cheveux let cheveux = XRegExp.exec(statString, XRegExp("kg,\\s+(?[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+),\\s+yeux", 'giu')); if (cheveux?.value) { actorData.cheveux = cheveux.value; } // Get yeux let yeux = XRegExp.exec(statString, XRegExp("yeux\\s+(?[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+), Beau", 'giu')); if (yeux?.value) { actorData.yeux = yeux.value; } // Get beauty let beaute = XRegExp.exec(statString, XRegExp("beauté\\s+(?\\d+)", 'giu')); if (beaute?.value) { actorData.beaute = Number(beaute.value); } } // Name is all string before ', né' let name = RdDStatBlockParser.extractName(actorType, statString); let newActor = RdDBaseActorReve.create({ name: name || "Importé", type: actorType, system: actorData, items: items }); // DUmp.... console.log(actorData); } static parseCreature(statString, actorData) { let plusDom = XRegExp.exec(statString, XRegExp("\\+dom\\s+(?[\\+\\-]?\\d+)", 'giu')); if (plusDom?.values) { actorData.attributs.plusdom.value = Number(plusDom.value); } let protection = XRegExp.exec(statString, XRegExp("protection\\s+(?[\\-]?\\d+)", 'giu')); if (protection?.value) { actorData.attributs.protection.value = Number(protection.value); } let endurance = XRegExp.exec(statString, XRegExp("endurance\\s+(?\\d+)", 'giu')); if (endurance?.value) { actorData.sante.endurance.value = Number(endurance.value); actorData.sante.endurance.max = Number(endurance.value); } let vie = XRegExp.exec(statString, XRegExp("vie\\s+(?\\d+)", 'giu')); if (vie.value) { actorData.sante.vie.value = Number(vie.value); actorData.sante.vie.max = Number(vie.value); } let vitesse = XRegExp.exec(statString, XRegExp("vitesse\\s+(?[\\d\\/]+)", 'giu')); if (vitesse?.value) { actorData.attributs.vitesse.value = vitesse.value; } } static parseEntite(statString, actorData) { let plusDom = XRegExp.exec(statString, XRegExp("\\+dom\\s+(?[\\+\\-]?\\d+)", 'giu')); if (plusDom?.values) { actorData.attributs.plusdom.value = Number(plusDom.value); } actorData.definition.categorieentite = 'cauchemar' actorData.definition.typeentite = ENTITE_NONINCARNE let endurance = XRegExp.exec(statString, XRegExp("endurance\\s+(?\\d+)", 'giu')); if (endurance?.value) { actorData.sante.endurance.value = Number(endurance.value); actorData.sante.endurance.max = Number(endurance.value); actorData.definition.typeentite = ENTITE_INCARNE } let vitesse = XRegExp.exec(statString, XRegExp("vitesse\\s+(?[\\d\\/]+)", 'giu')); if (vitesse?.value) { actorData.attributs.vitesse.value = vitesse.value; } } static parseActorType(statString) { let niveau = XRegExp.exec(statString, XRegExp("Niveau\\s+(?[\\+\\-]?\\d+)", 'giu')) let perception = XRegExp.exec(statString, XRegExp("perception\\s+(?\\d+)", 'giu')) if (perception?.value) { return "creature" } if (niveau?.value) { return "entite" } return "personnage" } static extractName(actorType, statString) { switch (actorType) { case "personnage": return RdDStatBlockParser.extractNamePersonnage(statString); case "creature": return RdDStatBlockParser.extractNameCreature(statString); } return RdDStatBlockParser.extractNameCreature(statString); } static extractNamePersonnage(statString) { let name = XRegExp.exec(statString, XRegExp("(?[\\p{Letter}\\s\\d]+),", 'giu')); if (!name?.value) { name = XRegExp.exec(statString, XRegExp("(?.+)\\s+taille", 'giu')); } return Misc.upperFirst(name?.value || "Importé"); } static extractNameCreature(statString) { const name = XRegExp.exec(statString, XRegExp("(?.+)\\s+taille", 'giu')); return Misc.upperFirst(name?.value || "Importé"); } } /************************************************************************************/ // Some internal test strings let statBlock01 = `+$16(/, baron de Sylvedire, né à l’heure du Roseau, 40 ans, 1m78, 65 kg, Beauté 13. TAILLE 10 Mêlée 14 APPARENCE 13 Tir 11 CONSTITUTION 12 Lancer 11 FORCE 12 Dérobée 13 AGILITÉ 16 Vie 11 DEXTÉRITÉ 13 Endurance 25 VUE 10 +dom 0 OUÏE 11 Protection 2 ou 4 ODO-GOÛT 9 cuir souple VOLONTÉ 14 ou cuir / métal INTELLECT 9 EMPATHIE 11 RÊVE 13 CHANCE 10 niv init +dom Épée dragonne +5 12 +3 Hache de bataille +6 13 +3 Bouclier moyen +5 Dague mêlée +4 11 +1 Corps à corps +4 11 (0) Esquive +8 Escalade, Saut +4 / Commerce +3 / Équitation +6 / Chirurgie 0 / Survie en extérieur +4 / Survie fo- rêt +6 / Acrobatie -2 / Métallurgie +2 / Natation +3 / Légendes -1 / Écriture -4 `; let statBlock02 = `/HVJDUGHV TAILLE 11 Mêlée 12 CONSTITUTION 11 Tir 11 FORCE 12 Lancer 11 AGILITÉ 12 Dérobée 11 DEXTERITÉ 11 Vie 11 VUE 11 Endurance 22 OUÏE 11 Vitesse 12 VOLONTÉ 10 +dom 0 Protection 4 cuir / métal niv init +dom Hache de bataille +4 10 +3 Bouclier moyen +4 Dague mêlée +3 9 +1 Arc +5 10 +2 Corps à corps +3 9 (0) Esquive avec armure +2 Course +1/ Vigilance +4 `; let statBlock03 = `rencontres sont laissées à /HVFKLHQVORXSVGXEDURQ chaque gardien des rêves. TAILLE 8 Vie 10 CONSTITUTION FORCE 12 11 Endurance Vitesse 12/38 21 /HVFKLHQV]RPELV PERCEPTION 13 +dom 0 VOLONTÉ 10 Protection 0 Les « monstres » apparaîtront un soir, durant RÊVE 10 l’heure du Serpent, et attaqueront les voya- niv init +dom geurs à leur campement. Si ces derniers ne Morsure 13 +4 10 +1 campent pas, ils apparaîtront tout de même à Esquive 11 +3 l’heure du Serpent. Le feu ne les effraie pas. Ils Course, Saut 12 +3 ne sont pas très rapides, mais en revanche, très Discrétion 12 +3 silencieux : ils n’aboient pas. Les voyageurs Vigilance 13 +3 `