Chroniques Oubliees: support parsing of JSON statblocks from co-drs.org

This commit is contained in:
Ulty
2023-02-17 09:58:53 +01:00
parent bc261ec6c9
commit 1cf4a54b75
2 changed files with 436 additions and 54 deletions
+1
View File
@@ -15,6 +15,7 @@ Le jeu complet, des suppléments et des téléchargements grat
# Notes de version
## v5.02 (2023-01-26)
* Support des statblocks JSON de CO-DRS
* Ajout des munitions
* Champs prédicat pour les armes portées
+435 -54
View File
@@ -4248,9 +4248,9 @@
};
function buildRegexp(obj) {
var res = "\\b(";
var premier = true;
for (var field in obj) {
let res = "\\b(";
let premier = true;
for (const field in obj) {
if (premier) {
res += field;
premier = false;
@@ -4342,7 +4342,7 @@
}];
function keyListToMap(l) {
var res = {};
let res = {};
l.forEach(function(s) {
s.keys.forEach(function(k) {
res[k] = s;
@@ -4396,25 +4396,405 @@
}
}
//parse les expressions du type 3d6+4
//renvoie un structure avec les champs nbDe, dice et bonus
function parseDice(expr, msg) {
let dm = {
nbDe: 0,
dice: 4,
bonus: 0
};
let exprDM = expr.trim().toLowerCase();
let indexD = exprDM.indexOf('d');
if (indexD > 0) {
dm.nbDe = parseInt(exprDM.substring(0, indexD));
if (isNaN(dm.nbDe) || dm.nbDe < 0) {
if (msg)
console.log("Expression de " + msg + ' ' + exprDM + " mal formée");
console.log(expr);
return;
}
exprDM = exprDM.substring(indexD + 1);
indexD = exprDM.search(/[+\-]/);
if (indexD <= 0) {
dm.dice = parseInt(exprDM);
if (isNaN(dm.dice) || dm.dice < 1) {
if (msg)
console.log("Nombre de faces incorrect dans l'expression de " + msg);
console.log(expr);
return;
}
return dm;
}
exprDM = exprDM.replace('+-', '-');
dm.dice = parseInt(exprDM.substring(0, indexD));
if (isNaN(dm.dice) || dm.dice < 1) {
if (msg)
console.log("Nombre de faces incorrect dans l'expression de " + msg);
console.log(expr);
return;
}
exprDM = exprDM.substring(indexD).trim();
}
dm.bonus = parseInt(exprDM);
if (isNaN(dm.bonus)) {
if (msg)
console.log("Expression de " + msg + " incorrecte");
console.log(expr);
return;
}
return dm;
}
const voiesDeCreatures = {
82: {
nom: 'Voie du prédateur',
1: {
actions: [{
titre: 'Embuscade des joueurs',
code: "!cof-surprise [[15+@{selected|DEX}]] --equipe joueurs"
}, {
titre: 'Attaque cible surprise',
code: "#Attaque -1 --sournoise 1 --if moins FOR --etat renverse --endif"
}, ],
},
2: {
predicats: 'devorer ',
},
3: {
predicats: 'gober ',
},
},
84: {
nom: 'Voie de la meute',
1: {
predicats: 'interchangeable ',
},
2: {
actions: [{
titre: 'Attaque en traître',
code: '#Attaque -1 --sournoise 2 --bonusAttaque 5'
}]
},
},
87: {
nom: 'Voie de la magie de combat',
2: {
actions:[{
titre: 'Armure magique',
code:'!cof-effet-combat armureMagique'
}]
},
},
88: {
nom: 'Voie des créatures volantes',
1: {
actions: [{
titre: 'Attaque en piqué',
code: '#Attaque -1 --bonusAttaque 5 --plus 1d6',
}],
2: {
predicats: 'agripper ',
},
},
},
89: {
nom: 'Voie des créatures magiques',
},
91: {
nom: 'Voie du colosse',
1: {
predicats: 'fauchage '
},
2: {
actions:[{
titre: 'Balayage',
code: '#Attaque -1 --target @{target|Cible 2|token_id}'
}]
},
3: {
predicats: 'projection '
}
},
92: {
nom: 'Voie du cogneur',
1: {
actions:[{
titre:'Charge',
code:'#Attaque -1 --m2d20 --pietine'
}],
},
2: {
predicats: 'peutEnrager '
},
3: {
actions:[{
titre:'Charge (remplace le rang 1)',
code:'#Attaque -1 --m2d20 --percute'
}],
}
},
93: {
nom: 'Voie du champion',
1: {
actions: [{
titre: 'Attaque imparable (L)',
code: '#Attaque -1 --m2d20'
}],
predicats: 'champion ',
},
2: {
predicats: 'riposte ',
},
3: {
predicats: 'hausserLeTon ',
},
},
94: {
nom: "Voie du chef d'armée",
1: {
predicats: 'sergent ',
},
2: {
actions: [{
titre:'Devenir capitaine de',
code:'!cof-capitaine @{selected|token_id} 2 --target @{target|token_id}',
}],
},
3: {
actions: [{
titre:'Devenir commandant de',
code:'!cof-capitaine @{selected|token_id} 3 --target @{target|token_id}',
}],
predicats: 'commandant ',
},
},
};
//Les champs JSON qui peuvent être directement transcrits
const statsJSON = {
str_mod: 'pnj_for',
dex_mod: 'pnj_dex',
con_mod: 'pnj_con',
int_mod: 'pnj_int',
wis_mod: 'pnj_sag',
cha_mod: 'pnj_cha',
creature_family: 'race',
profile: 'profil',
level: 'niveau',
size: 'taille',
profil: 'profil',
init: 'pnj_init',
defense: 'pnj_def',
health_point: 'PV_max',
dmg_reduction: 'RDS',
name: 'character_name',
archetype: 'archetype',
boss_rank: 'boss_rank',
boss_type: 'boss_type',
description: 'notes',
special_capabilities: 'capacites_pnj',
};
function htmlToText(msg) {
msg = msg.replace(/<p>|<strong>|<\/strong>|<em>|<\/em>|<br \/>/g, '');
msg = msg.replace(/<\/p>/g, '\n');
return msg;
}
function readJSONStats(stats, maxAttack, values) {
console.log(stats);
let se = {};
for (const f in statsJSON) {
if (stats[f] && stats[f].length > 0) {
let val = stats[f][0].value;
if (stats[f][0].label) val = stats[f][0].label;
if (val) {
if (stats[f][0].format == 'simple_html' ||
stats[f][0].format == 'basic_html') val = htmlToText(val);
se[statsJSON[f]] = val;
}
}
}
if (stats.comments && stats.comments.length > 0 && stats.comments[0].value) {
if (se.notes) se.notes += '\n';
else se.notes = '';
se.notes += htmlToText(stats.comments[0].value);
}
if (stats.paths && stats.paths.length > 0) {
let voies = stats.paths[0].value;
if (voies) {
if (se.capacites_pnj) se.capacites_pnj += '\n';
else se.capacites_pnj = '';
se.capacites_pnj += htmlToText(voies);
}
}
let predicats = '';
if (values.predicats_script) predicats = values.predicats_script + ' ';
if (stats.category && stats.category.length > 0) {
switch (stats.category[0].value) {
case 'alive': //rien à faire
break;
case 'humanoid':
predicats += 'humanoide ';
break;
case 'vegetative':
predicats += 'vegetatif ';
break;
case 'non_living':
predicats += 'nonVivant ';
break;
default:
console.log("Catégorie " + stats.category[0].value + " non reconnue");
}
}
if (stats.sup_abilities) {
stats.sup_abilities.forEach(function(sab) {
se['pnj_' + sab.label.toLowerCase() + '_sup'] = 'on';
se[sab.label.toLowerCase() + '_sup'] = '@{jetsup}';
});
}
if (se.niveau) se.niveau = parseInt(se.niveau);
se.PV = se.PV_max;
if (se.RDS) {
se.RDS = se.RDS.replace(/Armes magiques/, 'magique');
se.RDS = se.RDS.replace(/magie/g, 'magique');
}
if (stats.attacks && stats.attacks.length > 0) {
let atd = stats.attacks[0].data;
if (atd) {
atd.forEach(function(att) {
let nomAttaque = '';
if (att.name) nomAttaque = att.name;
let nbDe = 0;
let de = 4;
let bonusDM = 0;
if (att.dm) {
let d = parseDice(att.dm, "dégâts de l'attaque");
if (d) {
nbDe = d.nbDe;
de = d.dice;
bonusDM = d.bonus;
}
}
let bonusAtt = parseInt(att.test);
if (isNaN(bonusAtt)) {
console.log("Bonus d'attaque incorrect");
console.log(att);
bonusAtt = '';
}
let portee = parseInt(att.reach);
if (isNaN(portee)) portee = 0;
if (portee) {
//on va essayer d'enlever les indications de portée du nom de l'attaque.
let index = nomAttaque.indexOf('(' + portee);
if (index > 0) {
let index2 = nomAttaque.indexOf(')', index);
if (index2 > 0) {
if (index2 < nomAttaque.length - 1) {
nomAttaque = nomAttaque.substring(0, index) + nomAttaque.substring(index2 + 1);
} else {
nomAttaque = nomAttaque.substring(0, index);
}
nomAttaque = nomAttaque.trim();
}
}
}
const prefix = 'repeating_pnjatk_' + generateRowID() + '_arme';
maxAttack++;
se[prefix + 'label'] = maxAttack;
se[prefix + 'nom'] = nomAttaque;
se[prefix + 'atk'] = bonusAtt;
se[prefix + 'dmnbde'] = nbDe;
se[prefix + 'dmde'] = de;
se[prefix + 'dm'] = bonusDM;
if (att.special) se[prefix + 'spec'] = att.special;
if (portee) se[prefix + 'portee'] = portee;
// On essaie de trouver des valeurs pour le type de l'attaque
guessAttackType(nomAttaque, prefix, se, values);
});
}
}
let maxRangAction = parseInt(values.maxrangaction);
if (isNaN(maxRangAction)) maxRangAction = 0;
const addAction = function(a) {
maxRangAction++;
const prefix = 'repeating_actions_' + generateRowID() + '_action';
se[prefix + 'rang'] = maxRangAction;
for (const champ in a) {
se[prefix + champ] = a[champ];
}
};
if (stats.structured_capabilities) {
for (const vid in stats.structured_capabilities) {
let sc = stats.structured_capabilities[vid];
switch (sc.type) {
case 'creature':
let v = voiesDeCreatures[vid];
if (v) {
for (const rang in sc.capabilities) {
if (rang == 'nom') continue;
let r = v[rang];
if (r) {
if (r.actions) {
r.actions.forEach(addAction);
}
if (r.predicats) {
predicats += r.predicats;
}
}
}
}
break;
case 'character':
case 'prestige':
case 'race': //On ne sait pas encore faire
break;
default:
console.log("Voie non reconnue");
console.log(sc);
}
}
}
if (predicats) se.predicats_script = predicats;
if (maxAttack) se.max_attack_label = maxAttack;
if (maxRangAction) se.maxrangaction = maxRangAction;
setAttrs(se);
}
//Parsing des statblocks. Peut cascader
on('change:statblock', function(eventinfo) {
getAttrs(['max_attack_label', 'scriptVersion', 'scriptVersion_max', 'montrerarmeenmain'], function(values) {
var maxAttack = parseInt(values.max_attack_label);
getAttrs(['max_attack_label', 'scriptVersion', 'scriptVersion_max', 'montrerarmeenmain', 'predicats_script', 'maxrangaction'], function(values) {
let maxAttack = parseInt(values.max_attack_label);
if (isNaN(maxAttack)) maxAttack = 0;
var stats = eventinfo.newValue;
if (stats === '') return;
let stats = eventinfo.newValue;
if (stats === '' || stats === undefined) return;
if (stats.startsWith('{"name":')) {
let jstats;
try {
jstats = JSON.parse(stats);
} catch (e) {
console.log("Erreur de décodage JSON du stat block");
console.log(e);
jstats = undefined;
}
if (jstats) {
readJSONStats(jstats, maxAttack, values);
return;
}
}
stats = stats.replace(/\r/g, '');
//On enlève les caractères images des dernières versions de COF
stats = stats.replace(/\ue1e9/g, ''); //DEF
stats = stats.replace(/♥/g, ''); //PV
stats = stats.replace(/\ue0bf/g, ''); //Attaque
stats = stats.replace(/\, /g, ' ');
var rows = stats.split(/\n/);
var newAttrs = {};
var previousLine = ''; //Au cas d'attaque sur plusieurs lignes
var previousContainsDM = false;
var lastPrefix = ''; //Pour les suites d'attaque sur la ligne suivante
var firstLine = true; //si la première ligne ne correspond à rien, c'est le nom
let rows = stats.split(/\n/);
let newAttrs = {};
let previousLine = ''; //Au cas d'attaque sur plusieurs lignes
let previousContainsDM = false;
let lastPrefix = ''; //Pour les suites d'attaque sur la ligne suivante
let firstLine = true; //si la première ligne ne correspond à rien, c'est le nom
rows.forEach(function(row) {
if (row === '') {
previousLine = '';
@@ -4428,17 +4808,18 @@
previousLine += row + ' ';
previousContainsDM = true;
} else if (previousContainsDM || row.search(/\bDM\b/i) >= 0) { //Attaque
var currentRow = previousLine + row;
var nomAttaqueFin = currentRow.search(/\s(\+\d+|0\b|DM)/i);
var nomAttaque = currentRow.substring(0, nomAttaqueFin);
var portee = /\(\d+\s*(m|mètre|mètres|metre|metres)\)/i.exec(nomAttaque);
let currentRow = previousLine + row;
let nomAttaqueFin = currentRow.search(/\s(\+\d+|0\b|DM)/i);
let nomAttaque = currentRow.substring(0, nomAttaqueFin);
let portee =
/\(\d+\s*(m|mètre|mètres|metre|metres)\)/i.exec(nomAttaque);
if (portee) {
nomAttaque = nomAttaque.substring(0, portee.index).trim();
portee = parseInt(portee[0].substring(1));
if (isNaN(portee)) portee = 0;
}
currentRow = currentRow.substring(nomAttaqueFin).trim();
var bonusAtt = 0;
let bonusAtt = 0;
if (currentRow.startsWith('+')) {
currentRow = currentRow.substring(1);
bonusAtt = parseInt(currentRow);
@@ -4451,9 +4832,9 @@
} else if (currentRow.startsWith('0')) {
currentRow = currentRow.substring(1).trim();
}
var index = currentRow.search(/\bDM\b/i);
var specAtt = currentRow.substring(0, index).trim();
var specInAttackName = /\([^\)]*\)/i.exec(nomAttaque);
let index = currentRow.search(/\bDM\b/i);
let specAtt = currentRow.substring(0, index).trim();
let specInAttackName = /\([^\)]*\)/i.exec(nomAttaque);
if (specInAttackName) {
nomAttaque = nomAttaque.substring(0, specInAttackName.index).trim();
specAtt += ' ' + specInAttackName[0].substring(1, specInAttackName[0].length - 1);
@@ -4472,11 +4853,11 @@
if (index < 0) {
index = currentRow.length;
}
var nbDe = 0;
var de = 4;
var bonusDM = 0;
var dmExpr = currentRow.substring(0, index);
var indexDe = dmExpr.search(/d/i);
let nbDe = 0;
let de = 4;
let bonusDM = 0;
let dmExpr = currentRow.substring(0, index);
let indexDe = dmExpr.search(/d/i);
if (indexDe < 0) {
bonusDM = parseInt(dmExpr);
if (isNaN(bonusDM)) {
@@ -4495,7 +4876,7 @@
previousContainsDM = true;
return;
}
var indexBonusDM = dmExpr.search(/(\+|-)/);
let indexBonusDM = dmExpr.search(/(\+|-)/);
if (indexBonusDM >= 0) {
bonusDM = parseInt(dmExpr.substring(indexBonusDM));
if (isNaN(bonusDM)) {
@@ -4515,7 +4896,7 @@
if (index >= 0 && index < currentRow.length - 1) {
specAtt += currentRow.substring(index).trim();
}
var prefix = 'repeating_pnjatk_' + generateRowID() + '_arme';
const prefix = 'repeating_pnjatk_' + generateRowID() + '_arme';
maxAttack++;
newAttrs[prefix + 'label'] = maxAttack;
newAttrs[prefix + 'nom'] = nomAttaque;
@@ -4534,9 +4915,9 @@
newAttrs[lastPrefix + 'spec'] += row;
} else { //Pas attaque
lastPrefix = '';
var lexemes = [];
var lastMatch;
var match = patternStats.exec(row);
let lexemes = [];
let lastMatch;
let match = patternStats.exec(row);
while (match) {
if (lastMatch) {
lexemes.push({
@@ -4562,19 +4943,19 @@
}
} else previousLine = '';
lexemes.forEach(function(l) {
var lstat = l.match[0].toLowerCase();
var lattr = statsReconnues[lstat];
let lstat = l.match[0].toLowerCase();
let lattr = statsReconnues[lstat];
if (lattr === undefined && lstat == 'créature') lattr = statsReconnues.creature;
if (lattr === undefined) {
console.log("Erreur ! Pattern " + lstat + " non reconne. La ligne était " + l);
return;
}
var valAttr = row.substring(l.match.index + lstat.length, l.end).trim();
let valAttr = row.substring(l.match.index + lstat.length, l.end).trim();
valAttr = valAttr.replace(/\)$/, '');
if (lattr.search(/(for|dex|con|sag|int|cha)/) > 0) {
//Cas ou on rajoute une autre valeur pour la caractéristique
//par exemple pour le centaure
var caracParen = valAttr.search(/\([+-]\d/);
let caracParen = valAttr.search(/\([+-]\d/);
if (caracParen > 0) {
valAttr = valAttr.substring(0, caracParen - 1).trim();
}
@@ -4908,19 +5289,19 @@
on('change:attaque_risquee_check change:attaque_dm_temp_check', function(eventinfo) {
getAttrs(['attaque_risquee_check', 'attaque_dm_temp_check'], function(values) {
var attaque_risquee = parseInt(values.attaque_risquee_check);
var attaque_non_lethale = parseInt(values.attaque_dm_temp_check);
var updateArmes = function(section) {
let attaque_risquee = parseInt(values.attaque_risquee_check);
let attaque_non_lethale = parseInt(values.attaque_dm_temp_check);
const updateArmes = function(section) {
getSectionIDs('repeating_' + section, function(idArray) {
idArray.forEach(function(id) {
var prefix = 'repeating_' + section + '_' + id + '_';
const prefix = 'repeating_' + section + '_' + id + '_';
getAttrs([prefix + 'armeportee', prefix + 'armemodificateurs'], function(values) {
var portee = values[prefix + 'armeportee'];
var modifs = values[prefix + 'armemodificateurs'].split(',');
var bonus = 0;
let portee = values[prefix + 'armeportee'];
let modifs = values[prefix + 'armemodificateurs'].split(',');
let bonus = 0;
if (attaque_non_lethale) bonus = -2;
var attrs = {};
var dmTemp = attaque_non_lethale;
let attrs = {};
let dmTemp = attaque_non_lethale;
modifs.forEach(function(m) {
switch (m.trim()) {
case 'tempDmg':
@@ -5036,16 +5417,16 @@
on('change:repeating_pnjatk:armeportee change:repeating_pnjatk:armemodificateurs', function(eventinfo) {
getAttrs(['attaque_risquee_check', 'attaque_dm_temp_check', 'repeating_pnjatk_armeportee', 'repeating_pnjatk_armemodificateurs'], function(values) {
var attaque_risquee = parseInt(values.attaque_risquee_check);
var attaque_non_lethale = parseInt(values.attaque_dm_temp_check);
var portee = values.repeating_pnjatk_armeportee;
var modifs = values.repeating_pnjatk_armemodificateurs.split(',');
var bonus = 0;
let attaque_risquee = parseInt(values.attaque_risquee_check);
let attaque_non_lethale = parseInt(values.attaque_dm_temp_check);
let portee = values.repeating_pnjatk_armeportee;
let modifs = values.repeating_pnjatk_armemodificateurs.split(',');
let bonus = 0;
if (attaque_non_lethale) bonus = -2;
var attrs = {};
var dmTemp = attaque_non_lethale;
var nbDes = 1; //Nombre de dés pour l'attaque
var plusFort = true;
let attrs = {};
let dmTemp = attaque_non_lethale;
let nbDes = 1; //Nombre de dés pour l'attaque
let plusFort = true;
attrs.repeating_pnjatk_armedegats = DEGATSPNJ;
attrs.repeating_pnjatk_armepoudre = '{{poudre=}}';
attrs.repeating_pnjatk_armereussiteauto = '{{attaqueautomatique=}}';