/************************************************************************************/ // Some internal test strings let strfr = `LA TERREUR DE LA TEUFEL, TROLL DE RIVIÈRE RUSÉ M CC CT F E I Ag Dex Int FM Soc B 4 40 15 55 45 20 15 15 30 20 5 38 Traits : Amphibie, Arme +9, Armure (2), Dur à Cuire, Insensible à la douleur, Morsure +8, Régénération, Taille (Grande), Vision Nocturne, Vomissement` let str1 = `JABBERSLYTHE M WS BS S T I Agi Dex Int WP Fel W 7 45 40 55 50 20 35 - 10 20 - 20 Traits: Armour 3, Bestial, Bite+9, Bounce, Corrosive Blood, Distracting, Infected, Maddening Aura (see page 17), Night Vision, Size (Enormous), Tail +8, Tongue Attack +5 (12), Venom, Weapon +9. `; let str = `REINER AND DIETER LEDERMANN SMUGGLERS (BRASS 3) M WS BS S T I Agi Dex Int WP Fel W 4 33 33 32 35 38 41 39 33 37 38 12 Traits: Weapon (Dagger +5, Sword +7) Skills: Bribery 43, Charm 43, Cool 42, Consume Alcohol 45, Gossip 43, Haggle 43, Lore (Local 38), Perception 43, Secret Signs (Smuggler) 37 Talents: Briber, Criminal, Dealmaker, Etiquette (Criminals, Doktor, Guilder) Trappings: Dagger, Hand Weapon (Sword) ` //import ItemWfrp4e from "/systems/wfrp4e/modules/item/item-wfrp4e.js" //import ItemWfrp4e from "/systems/wfrp4e/wfrp4e.js" /************************************************************************************/ import "./xregexp-all.js"; const us_carac = 'm\\s+ws\\s+bs\\s+s\\s+t\\s+i\\s+agi?\\s+dex\\s+int\\s+\\wp\\s+fel\\s+w'; const fr_carac = 'm\\s+cc\\s+ct\\s+f\\s+e\\s+i\\s+agi?\\s+dex\\s+int\\s+fm\\s+soc\\s+b'; const carac_val = '(?[0-9\\-]+)\\s+(?[0-9\\-]+)\\s+(?[0-9\\-]+)\\s+(?[0-9\\-]+)\\s+(?[0-9\\-]+)\\s+(?[0-9\\-]+)\\s+(?[0-9\\-]+)\\s+(?[0-9\\-]+)\\s+(?[0-9\\-]+)\\s+(?[0-9\\-]+)\\s+(?[0-9\\-]+)\\s+(?[0-9\\-\*]+)'; const name_val = '(?[a-zA-Z\\s\\-,]*)[\\s\\r\\na-zA-Z]*(?.*|[\\(\\)a-z0-9]+)'; let sectionDataFR = [ { name: "trait", toFind: "Traits\\s*:", secondParse: '(?[a-zàéè\\s]*)[\\s\\+]*(?.*|[\\+0-9]+)', index: -1 }, { name: "skill", toFind: "Compétences\\s*:", secondParse: '(?[a-zàéè\\s\\(\\)]*)[\\s\\+]*(?.*|[0-9]+)', index: -1 }, { name: "talent", toFind: "Talents\\s*:", secondParse: '(?[a-zàéè\\-\\s!/]*)[\\s\\+]*(?.*|[0-9]+)', index: -1 }, { name: "mutation", toFind: "Mutations\\s*:", secondParse: '(?[a-zàéè\\s]*)[\\s\\+]*(?.*|[0-9]+)', index: -1 }, { name: "trapping", toFind: "Equiement\\s*:", secondParse: '(?[a-zàéè\\s]*)[\\s\\+]*(?.*|[0-9]+)', index: -1 } ]; let sectionDataUS = [ { name: "trait", toFind: "Traits\\s*:", secondParse: '(?[a-z\\s]*)[\\s\\+]*(?.*|[\\+0-9]+)', index: -1 }, { name: "skill", toFind: "Skills\\s*:", secondParse: '(?[a-z\\s\\(\\)]*)[\\s\\+]*(?.*|[0-9]+)', index: -1 }, { name: "talent", toFind: "Talents\\s*:", secondParse: '(?[a-z\\-\\s!/]*)[\\s\\+]*(?.*|[0-9]+)', index: -1 }, { name: "mutation", toFind: "Mutations\\s*:", secondParse: '(?[a-z\\s]*)[\\s\\+]*(?.*|[0-9]+)', index: -1 }, { name: "trapping", toFind: "Trappings\\s*:", secondParse: '(?[a-z\\s]*)[\\s\\+]*(?.*|[0-9]+)', index: -1 } ]; let regSep = XRegExp('\\s*,\\s*', 'gi'); // Term separator, with auto trim let regLine1 = XRegExp('[\\r\\n\\.]', 'gi'); // Term separator, with auto trim let regName = XRegExp(name_val, 'gi'); /************************************************************************************/ async function __findItem(itemName, itemType, location = null) { let toSearch = itemName.toLowerCase().trim(); let items = game.items.contents.filter(i => i.type == itemType) // Search imported items first for (let i of items) { if (i.name == itemName && i.type == itemType) return i; } let itemList // find pack -> search pack -> return entity if (location) { let pack = game.packs.find(p => { location.split(".")[0] == p.metadata.package && location.split(".")[1] == p.metadata.name }) if (pack) { await pack.getIndex().then(index => itemList = index); let searchResult = itemList.find(t => (t.translated && t.originalName.toLowerCase() == toSearch) || (t.name.toLowerCase() == toSearch) ); if (searchResult) return await pack.getDocument(searchResult._id) } } // If all else fails, search each pack for (let p of game.wfrp4e.tags.getPacksWithTag(itemType)) { await p.getIndex().then(index => itemList = index); let searchResult = itemList.find(t => (t.translated && t.originalName.toLowerCase() == toSearch) || (t.name.toLowerCase() == toSearch) ); if (searchResult) return await p.getDocument(searchResult._id) } } /************************************************************************************/ async function __findSkill(skillName, value = undefined) { let toSearch = skillName.toLowerCase().trim(); let parseStr = '(?[a-z\\s]*)[\\s\\+]*(?[a-z\\s\\(\\)]*)'; let skillSplit = XRegExp.exec(skillName, XRegExp(parseStr, 'gi')); // First try world items let worldItem = game.items.contents.filter(i => i.type == "skill" && i.name.toLowerCase() == toSearch)[0]; if (worldItem) return worldItem; let packs = game.wfrp4e.tags.getPacksWithTag("skill"); for (let pack of packs) { let skillList = await pack.getIndex(); // Search for specific skill (won't find unlisted specializations) let searchResult = skillList.find(s => (s.translated && s.originalName.toLowerCase() == toSearch) || (s.name.toLowerCase() == toSearch ) ); if (!searchResult) { let toSearchClean = toSearch.split("(")[0].trim(); searchResult = skillList.find(s => (s.translated && s.originalName.toLowerCase().split("(")[0].trim() == toSearchClean) || (s.name.toLowerCase().split("(")[0].trim() == toSearchClean) ); } if (searchResult) { let dbSkill; await pack.getDocument(searchResult._id).then(packSkill => dbSkill = packSkill); if (skillSplit.specialized && ( dbSkill.name.includes('()') || dbSkill.name.includes('( )' ) ) ) { let spec = XRegExp.replace(skillSplit.specialized, "(", ""); spec = XRegExp.replace(spec, ")", ""); let skillSplit2 = XRegExp.exec(dbSkill.name, XRegExp(parseStr, 'gi')); dbSkill.update( { name: skillSplit2.name + '(' + game.i18n.localize( spec.trim() ) + ')' } ); } //game.babele.translate('wfrp4e-core.skills', dbSkill); return dbSkill; } } throw "Could not find skill (or specialization of) " + skillName + " in compendum or world" } /************************************************************************************/ async function __findTalent(talentName) { let parseStr = '(?[a-z\\s]*)[\\s\\+]*(?[a-z\\s\\(\\)]*)'; let talentSplit = XRegExp.exec(talentName, XRegExp(parseStr, 'gi')); let toSearch = talentSplit.name.toLowerCase().trim(); // First try world items let worldItem = game.items.contents.filter(i => i.type == "talent" && i.name.toLowerCase() == toSearch)[0]; if (worldItem) return worldItem; let packs = game.wfrp4e.tags.getPacksWithTag("talent"); for (let pack of packs) { let talentList = await pack.getIndex(); // Search for specific skill (won't find unlisted specializations) let searchResult = talentList.find(s => (s.translated && s.originalName.toLowerCase() == toSearch) || (s.name.toLowerCase() == toSearch ) ); if (!searchResult) { let toSearchClean = toSearch.split("(")[0].trim(); searchResult = talentList.find(s => (s.translated && s.originalName.toLowerCase().split("(")[0].trim() == toSearchClean) || (s.name.toLowerCase().split("(")[0].trim() == toSearchClean) ); } if (searchResult) { let dbTalent; await pack.getDocument(searchResult._id).then(packTalent => dbTalent = packTalent); if ( talentSplit.specialized ) { let spec = XRegExp.replace(talentSplit.specialized, "(", ""); spec = XRegExp.replace(spec, ")", ""); dbTalent.update( { name: talentSplit.name + '(' + game.i18n.localize( spec.trim() ) + ')' } ); } return dbTalent; } } throw "Could not find talent (or specialization of) " + talentName + " in compendium or world" } /************************************************************************************/ function __patchName ( name) { if (name.toLowerCase == 'magic sense') name = 'Magical Sense' return name } /************************************************************************************/ export default async function statParserFR(statString, type = "npc") { let model = duplicate(game.system.model.Actor[type]); // Patch wront/strange carac value before processing statString = statString.replace(/ –/g, " 0") let statNameReg = us_carac let sectionData = sectionDataUS // Detect French stat block if (statString.includes('CC') && statString.includes('CT') && statString.includes('FM')) { ui.notifications.warn("Le parsing de stablock en Français n'est pas encore prêt") statNameReg = fr_carac sectionData = sectionDataFR } let reg1 = XRegExp(statNameReg, 'gi') let res = reg1.test(statString) if (res) { //stat block identified go on // Extract the name let res1 = XRegExp.exec(statString, reg1) console.log("REG", res1) let pnjStr = statString.substring(0, res1.index) let nameRes = XRegExp.exec(pnjStr, regName) console.log(nameRes) if (nameRes.tiers && nameRes.tiers.length > 0 && hasProperty(model, "details.status.value")) { let regTiers = XRegExp("(?[A-Za-z]*)\\s+(?[0-9]*)"); let resTiers = XRegExp.exec(nameRes.tiers, regTiers); console.log(resTiers); model.details.status.value = game.i18n.localize(resTiers.name.trim()) + " " + resTiers.level; } // Compute the PNJ name let pnjName = nameRes.name.split("—")[0].split(" ").filter(f => !!f); pnjName = pnjName.map(word => { if (word == "VON") return word.toLowerCase(); word = word.toLowerCase(); word = word[0].toUpperCase() + word.substring(1, word.length); return word; }) pnjName = pnjName.join(" ") // Get the carac values let reg2 = XRegExp(carac_val, 'gi') let resCarac = XRegExp.exec(statString, reg2) // resr contains all carac found // Setup carac //console.log("CARAC", resCarac) if (resCarac["Agi"]) resCarac["Ag"] = resCarac["Agi"] // Auto patch model.details.move.value = Number(resCarac["m"]) for (let key in model.characteristics) { if (resCarac[key] === '-') resCarac[key] = 0 model.characteristics[key].initial = Number(resCarac[key]) } //console.log("CARAC", model.characteristics); // Search position of skills/talents/... for (let def of sectionData) { def.regDef = XRegExp(def.toFind, 'gi'); let res = XRegExp.exec(statString, def.regDef); if (res) def.index = res.index; // Get the index in the string //console.log(" Parsing", def.name, res); } // Sort to split position of various substring sectionData.sort(function (a, b) { return a.index - b.index; }); let globalItemList = []; // Then loop again and process each item type for (let i = 0; i < sectionData.length; i++) { let def = sectionData[i]; if (def.index > -1) { let maxIndex = statString.length if (sectionData[i + 1] && sectionData[i + 1].index > -1) maxIndex = sectionData[i + 1].index def.substring = statString.substring(def.index, maxIndex) def.substring = XRegExp.replace(def.substring, def.regDef, "") def.substring = XRegExp.replace(def.substring, regLine1, " ") // At this point, def.substring contains the items list as a string // Then create a table of it in termList, with specific sub-parsing rules let termList = XRegExp.split(def.substring, regSep); for (let name of termList) { let itemFound, subres, value; if (def.secondParse) { subres = XRegExp.exec(name, XRegExp(def.secondParse, 'gi')) name = subres.name.trim().replace("\n", "").replace("\r", "") value = XRegExp.replace(subres.value, "(", "") value = XRegExp.replace(value, ")", "") } name = __patchName(name) if (def.name == 'trait') { try { itemFound = await __findItem(name, "trait") } catch { } if (itemFound) itemFound = itemFound.toObject() if (itemFound && value && value.length > 0) { if (name.toLowerCase() == 'weapon' || name.toLowerCase() == "bite" || name.toLowerCase() == "tail" || name.toLowerCase() == 'arme' || name.toLowerCase() == "morsure" || name.toLowerCase() == "queue") { itemFound.system.specification.value = Number(value) - Math.floor( Number(model.characteristics.s.initial) / 10) } else { itemFound.system.specification.value = game.i18n.localize(value) } } if (!itemFound) ui.notifications.error("Trait non trouvé, à ajouter manuellement : " + name, { permanent: true }) } else if (def.name == 'skill') { try { itemFound = await __findSkill(name, value); } catch { } if (itemFound) itemFound = itemFound.toObject(); if (itemFound && subres && value) { itemFound.system.advances.value = Number(value) - Number(model.characteristics[itemFound.system.characteristic.value].initial); } if (!itemFound) ui.notifications.error("Compétence non trouvée, à ajouter manuellement : " + name, { permanent: true }) } else if (def.name == 'talent') { try { itemFound = await __findTalent(name); } catch { } if (itemFound) itemFound = itemFound.toObject(); if (itemFound && subres && value) itemFound.system.advances.value = Number(value); if (!itemFound) ui.notifications.error("Talent non trouvé, à ajouter manuellement : " + name, { permanent: true }) } else if (def.name == 'trapping') { try { itemFound = await __findItem(name, "trapping"); } catch { } if (!itemFound && name) { itemFound = new game.entities.ItemWfrp4e({ img: "systems/wfrp4e/icons/blank.png", name: name, type: "trapping", data: game.system.model.Item.trapping }) itemFound.system.trappingType.value = "misc" } if (itemFound) itemFound = itemFound.toObject(); } else if (def.name == 'mutation') { try { itemFound = await __findItem(name, "mutation"); } catch { } if (itemFound) itemFound = itemFound.toObject(); } if (itemFound) globalItemList.push(itemFound); } } } let moneyItems = await game.wfrp4e.utility.allMoneyItems() || []; moneyItems = moneyItems.sort((a, b) => (a.system.coinValue.value > b.system.coinValue.value) ? -1 : 1); moneyItems.forEach(m => m.system.quantity.value = 0) globalItemList = globalItemList.concat(moneyItems); //console.log("My liste :", globalItemList); let name = pnjName; let effects = globalItemList.reduce((total, globItem) => total.concat(globItem.effects), []) effects = effects.filter(e => !!e) effects = effects.filter(e => e.transfer) return { name, type, data: model, items: globalItemList, effects } } // If the carac string has not been found ui.notifications.error("Impossible de convertir ces statitiques, les caractéristiques n'ont pas été trouvées", { permanent: true }) }