/************************************************************************************/ // Some internal test strings 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 "./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+ws\\s+bs\\s+s\\s+t\\s+i\\s+agi?\\s+dex\\s+int\\s+\\wp\\s+fel\\s+w'; 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 sectionData = [ { 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: "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) { itemName = itemName.trim(); let items = game.items.entities.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) { if (pack.translations[itemName]) { let translItemName = pack.translations[itemName].name; await pack.getIndex().then(index => itemList = index); let searchResult = itemList.find(t => t.name == translItemName) if (searchResult) return await pack.getEntity(searchResult._id) } } } // If all else fails, search each pack for (let p of game.packs.filter(p => p.metadata.tags && p.metadata.tags.includes(itemType))) { if (p.translations[itemName]) { let translItemName = p.translations[itemName].name; await p.getIndex().then(index => itemList = index); let searchResult = itemList.find(t => t.name == translItemName) if (searchResult) return await p.getEntity(searchResult._id) } } } /************************************************************************************/ async function __findSkill(skillName) { skillName = skillName.trim(); // First try world items let worldItem = game.items.entities.filter(i => i.type == "skill" && i.name == skillName)[0]; if (worldItem) return worldItem let skillList = []; let packs = game.packs.filter(p => p.metadata.tags && p.metadata.tags.includes("skill")) for (let pack of packs) { if ( pack.translations[skillName] ) { let translSkillName = pack.translations[skillName].name; skillList = await pack.getIndex() // Search for specific skill (won't find unlisted specializations) let searchResult = skillList.find(s => s.name == translSkillName) if (!searchResult) searchResult = skillList.find(s => s.name.split("(")[0].trim() == skillName.split("(")[0].trim()) if (searchResult) { let dbSkill; await pack.getEntity(searchResult._id).then(packSkill => dbSkill = packSkill); dbSkill.data.name = translSkillName; // This is important if a specialized skill wasn't found. Without it, would be added instead of return dbSkill; } } } throw "Could not find skill (or specialization of) " + skillName + " in compendum or world" } /************************************************************************************/ async function __findTalent(talentName) { talentName = talentName.trim(); // First try world items let worldItem = game.items.entities.filter(i => i.type == "talent" && i.name == talentName)[0]; if (worldItem) return worldItem let talentList = []; let packs = game.packs.filter(p => p.metadata.tags && p.metadata.tags.includes("talent")) for (let pack of packs) { if ( pack.translations[talentName] ) { let translTalentName = pack.translations[talentName].name; talentList = await pack.getIndex() // Search for specific talent (won't find unlisted specializations) let searchResult = talentList.find(t => t.name == translTalentName) if (!searchResult) searchResult = talentList.find(t => t.name.split("(")[0].trim() == talentName.split("(")[0].trim()) if (searchResult) { let dbTalent; await pack.getEntity(searchResult._id).then(packTalent => dbTalent = packTalent); dbTalent.data.name = translTalentName; // This is important if a specialized talent wasn't found. Without it, would be added instead of return dbTalent; } } } throw "Could not find talent (or specialization of) " + talentName + " in compendium or world" } /************************************************************************************/ export default async function statParserFR( statString, type = "npc") { let model = duplicate(game.system.model.Actor[type]); let reg1 = XRegExp(us_carac, 'gi'); let res = reg1.test(statString); if (res) { //stat block identified go on // Extract the name let res1 = XRegExp.exec(statString, reg1); 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 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; if (def.secondParse) { subres = XRegExp.exec( name, XRegExp(def.secondParse, 'gi') ); name = subres.name.trim(); } if ( def.name == 'trait') { try { itemFound = await __findItem(name, "trait"); } catch {} if ( itemFound && subres && subres.value.length > 0 ) { subres.value = XRegExp.replace(subres.value, "(", ""); subres.value = XRegExp.replace(subres.value, ")", ""); itemFound.data.data.specification.value = game.i18n.localize( subres.value); } if (!itemFound) ui.notifications.error("Trait non trouvé, à ajouter manuellemen : " + name, { permanent: true }) } else if ( def.name == 'skill') { try { itemFound = await __findSkill(name); } catch {} if ( itemFound && subres && subres.value) { itemFound.data.data.advances.value = Number(subres.value) - Number(resCarac[itemFound.data.data.characteristic.value]); } 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 && subres && subres.value) itemFound.data.data.advances.value = Number(subres.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) { itemFound = new game.wfrp4e.entities.ItemWfrp4e({ img: "systems/wfrp4e/icons/blank.png", name: name, type: "trapping", data: game.system.model.Item.trapping }) itemFound.data.data.trappingType.value = "misc" } } if (itemFound) globalItemList.push( itemFound ); } } } let moneyItems = await game.wfrp4e.utility.allMoneyItems() || []; moneyItems = moneyItems.sort((a, b) => (a.data.coinValue.value > b.data.coinValue.value) ? -1 : 1); moneyItems.forEach(m => m.data.quantity.value = 0) globalItemList = globalItemList.concat(moneyItems); //console.log("My liste :", globalItemList); let name = pnjName; return { name, type, data: model, items: globalItemList } } // 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 } ) } /************************************************************************************/ Hooks.once('ready', () => { //var fullskills = game.packs.get('wfrp4e-core.skills'); //console.log("Skills", game.wfrp4e.apps.StatBlockParser.prototype); } )