Vincent Vandemeulebrouck
57d35a0f9a
Le caractère utilisé dans le texte n'est pas l'apostrophe simple informatique (utilisé dans les compendiums), et pas transformé par toLowerCaseNoAccent...
578 lines
16 KiB
JavaScript
578 lines
16 KiB
JavaScript
/************************************************************************************/
|
||
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";
|
||
import { RdDItemTete } from "../item/tete.js";
|
||
|
||
const WHITESPACES = "\\s+"
|
||
const NUMERIC = "[\\+\\-]?\\d+"
|
||
const NUMERIC_VALUE = "(?<value>" + NUMERIC + ")"
|
||
|
||
const XREGEXP_COMP_CREATURE = WHITESPACES + "(?<carac>\\d+)"
|
||
+ WHITESPACES + NUMERIC_VALUE
|
||
+ "(" + WHITESPACES + "(?<init>\\d+)?\\s+?(?<dommages>[\\+\\-]?\\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 = "(?<voies>[OHNT](\\/[OHNT])*)"
|
||
const XREGEXP_SORT_NAME = "(?<name>[^\\(]+)"
|
||
const XREGEXP_SORT_CASE = "\\((?<case>[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+)\\)";
|
||
|
||
const XREGEXP_SORT = "(" + XREGEXP_SORT_VOIE
|
||
+ WHITESPACES + XREGEXP_SORT_NAME
|
||
+ WHITESPACES + XREGEXP_SORT_CASE
|
||
+ WHITESPACES + "R(?<diff>([\\-\\d]+|(\\w|\\s)+))"
|
||
+ WHITESPACES + "r(?<reve>(\\d+(\\+)?|\\s\\w+))"
|
||
+ "(" + WHITESPACES + "\\+(?<bonus>\\d+)\\s?%" + WHITESPACES + "en" + WHITESPACES + "(?<bonuscase>[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: `
|
||
<div>
|
||
<p>Coller le texte de la stat ici</p>
|
||
<textarea id="statBlock" style="width: 100%; height: 200px;"></textarea>
|
||
</div>
|
||
`,
|
||
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) {
|
||
|
||
//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 type = RdDStatBlockParser.parseActorType(statString);
|
||
|
||
// Now start carac
|
||
let actorData = foundry.utils.deepClone(game.model.Actor[type]);
|
||
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+(?<value>\\d+)", 'giu'));
|
||
if (carac?.value) {
|
||
actorData.carac[key].value = Number(carac.value);
|
||
}
|
||
}
|
||
|
||
// If creature we need to setup additionnal fields
|
||
switch (type) {
|
||
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(type);
|
||
//console.log("Competences : ", competences);
|
||
for (let comp of competences) {
|
||
let compMatch = XRegExp.exec(statString, XRegExp(comp.name + compParser[type], 'giu'));
|
||
if (compMatch) {
|
||
comp = comp.toObject()
|
||
comp.system.niveau = Number(compMatch.value);
|
||
if (type == "creature" || type == "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 (type == "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+(?<value>\\+\\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);
|
||
}
|
||
}
|
||
|
||
|
||
if (type == "personnage") {
|
||
await RdDStatBlockParser.parseHautReve(statString, actorData, items);
|
||
RdDStatBlockParser.parsePersonnage(statString, actorData);
|
||
}
|
||
|
||
let name = RdDStatBlockParser.extractName(type, statString);
|
||
|
||
let newActor = await RdDBaseActorReve.create({ name, type: type, system: actorData, items });
|
||
await newActor.remiseANeuf()
|
||
// DUmp....
|
||
console.log(actorData);
|
||
}
|
||
|
||
static async parseHautReve(statString, actorData, items) {
|
||
let hautRevant = false;
|
||
// Attemp to detect spell
|
||
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, 'gu' /* keep case sensitive to match the spell draconic skill */),
|
||
function (matchSort, i) {
|
||
const sortName = Grammar.toLowerCaseNoAccent(matchSort.name).trim().replace("’", "'");
|
||
let sort = sorts.find(s => Grammar.toLowerCaseNoAccent(s.name) == sortName)
|
||
if (sort) {
|
||
hautRevant = true;
|
||
sort = sort.toObject();
|
||
if (matchSort.bonus && matchSort.bonuscase) {
|
||
sort.system.bonuscase = `${matchSort.bonuscase}:${matchSort.bonus}`;
|
||
}
|
||
items.push(sort);
|
||
}
|
||
else{
|
||
ui.notifications.warn(`Impossible de trouver le sort ${matchSort.name} / ${sortName}`)
|
||
console.warn(`Impossible de trouver le sort ${matchSort.name} / ${sortName}`)
|
||
}
|
||
});
|
||
|
||
if (hautRevant) {
|
||
const donHR = await RdDItemTete.teteDonDeHautReve();
|
||
if (donHR) {
|
||
items.push(donHR.toObject());
|
||
}
|
||
|
||
const demiReve = XRegExp.exec(statString, XRegExp("Demi-rêve\\s+(?<value>[A-M]\\d{1,2})", 'giu'))
|
||
actorData.reve.tmrpos.coord = demiReve?.value ?? 'A1'
|
||
}
|
||
}
|
||
|
||
static parsePersonnage(statString, actorData) {
|
||
actorData.reve.seuil.value = actorData.carac.reve.value
|
||
|
||
const reveActuel = XRegExp.exec(statString, XRegExp("Rêve actuel\\s+(?<value>\\d+)", 'giu'))
|
||
actorData.reve.reve.value = reveActuel?.value ? Number(reveActuel.value) : actorData.reve.seuil.value
|
||
|
||
const feminin = XRegExp.exec(statString, XRegExp("né(?<value>e?) à", 'giu'));
|
||
actorData.sexe = (feminin?.value == 'e') ? 'féminin' : 'masculin';
|
||
|
||
// Get hour name : heure du XXXXX
|
||
const heure = XRegExp.exec(statString, XRegExp("heure (du|de la|des|de l\')\\s*(?<value>[A-Za-zÀ-ÖØ-öø-ÿ\\s]+),", 'giu'));
|
||
actorData.heure = this.getHeureKey(heure?.value || "Vaisseau");
|
||
|
||
// Get age
|
||
const age = XRegExp.exec(statString, XRegExp("(?<value>\\d+) ans", 'giu'));
|
||
if (age?.value) {
|
||
actorData.age = Number(age.value);
|
||
}
|
||
// Get height
|
||
const taille = XRegExp.exec(statString, XRegExp("(?<value>\\d+m\\d+)", 'giu'));
|
||
if (taille?.value) {
|
||
actorData.taille = taille.value;
|
||
}
|
||
// Get weight
|
||
const poids = XRegExp.exec(statString, XRegExp("(?<value>\\d+) kg", 'giu'));
|
||
if (poids?.value) {
|
||
actorData.poids = poids.value;
|
||
}
|
||
// Get cheveux
|
||
const cheveux = XRegExp.exec(statString, XRegExp("kg,\\s+(?<value>[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+),\\s+yeux", 'giu'));
|
||
if (cheveux?.value) {
|
||
actorData.cheveux = cheveux.value;
|
||
}
|
||
// Get yeux
|
||
const yeux = XRegExp.exec(statString, XRegExp("yeux\\s+(?<value>[A-Za-zÀ-ÖØ-öø-ÿ\\s\\-]+), Beau", 'giu'));
|
||
if (yeux?.value) {
|
||
actorData.yeux = yeux.value;
|
||
}
|
||
|
||
// Get beauty
|
||
const beaute = XRegExp.exec(statString, XRegExp("beauté\\s+(?<value>\\d+)", 'giu'));
|
||
if (beaute?.value) {
|
||
actorData.beaute = Number(beaute.value);
|
||
}
|
||
}
|
||
|
||
static parseCreature(statString, actorData) {
|
||
let plusDom = XRegExp.exec(statString, XRegExp("\\+dom\\s+(?<value>[\\+\\-]?\\d+)", 'giu'));
|
||
if (plusDom?.values) {
|
||
actorData.attributs.plusdom.value = Number(plusDom.value);
|
||
}
|
||
let protection = XRegExp.exec(statString, XRegExp("protection\\s+(?<value>[\\-]?\\d+)", 'giu'));
|
||
if (protection?.value) {
|
||
actorData.attributs.protection.value = Number(protection.value);
|
||
}
|
||
let endurance = XRegExp.exec(statString, XRegExp("endurance\\s+(?<value>\\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+(?<value>\\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+(?<value>[\\d\\/]+)", 'giu'));
|
||
if (vitesse?.value) {
|
||
actorData.attributs.vitesse.value = vitesse.value;
|
||
}
|
||
}
|
||
|
||
static parseEntite(statString, actorData) {
|
||
let plusDom = XRegExp.exec(statString, XRegExp("\\+dom\\s+(?<value>[\\+\\-]?\\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+(?<value>\\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+(?<value>[\\d\\/]+)", 'giu'));
|
||
if (vitesse?.value) {
|
||
actorData.attributs.vitesse.value = vitesse.value;
|
||
}
|
||
}
|
||
|
||
static parseActorType(statString) {
|
||
let niveau = XRegExp.exec(statString, XRegExp("Niveau\\s+(?<value>[\\+\\-]?\\d+)", 'giu'))
|
||
let perception = XRegExp.exec(statString, XRegExp("perception\\s+(?<value>\\d+)", 'giu'))
|
||
if (perception?.value) {
|
||
return "creature"
|
||
}
|
||
if (niveau?.value) {
|
||
return "entite"
|
||
}
|
||
return "personnage"
|
||
}
|
||
|
||
static extractName(actorType, statString) {
|
||
switch (actorType) {
|
||
case "personnage":
|
||
// Name is all string before first comma ','
|
||
const namePersonnage = XRegExp.exec(statString, XRegExp("(?<value>[\\p{Letter}\\s\\d]+),", 'giu'));
|
||
if (namePersonnage?.value) {
|
||
return Misc.upperFirst(namePersonnage?.value);
|
||
}
|
||
}
|
||
const name = XRegExp.exec(statString, XRegExp("(?<value>.+)\\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
|
||
`
|