export default class CharacterImporter { // transform JSON from sw5e.com to Foundry friendly format // and insert new actor static async transform(rawCharacter) { const sourceCharacter = JSON.parse(rawCharacter); //source character const details = { species: sourceCharacter.attribs.find((e) => e.name == "race").current, background: sourceCharacter.attribs.find((e) => e.name == "background").current, alignment: sourceCharacter.attribs.find((e) => e.name == "alignment").current }; const hp = { value: sourceCharacter.attribs.find((e) => e.name == "hp").current, min: 0, max: sourceCharacter.attribs.find((e) => e.name == "hp").current, temp: sourceCharacter.attribs.find((e) => e.name == "hp_temp").current }; const abilities = { str: { value: sourceCharacter.attribs.find((e) => e.name == "strength").current, proficient: sourceCharacter.attribs.find((e) => e.name == "strength_save_prof").current ? 1 : 0 }, dex: { value: sourceCharacter.attribs.find((e) => e.name == "dexterity").current, proficient: sourceCharacter.attribs.find((e) => e.name == "dexterity_save_prof").current ? 1 : 0 }, con: { value: sourceCharacter.attribs.find((e) => e.name == "constitution").current, proficient: sourceCharacter.attribs.find((e) => e.name == "constitution_save_prof").current ? 1 : 0 }, int: { value: sourceCharacter.attribs.find((e) => e.name == "intelligence").current, proficient: sourceCharacter.attribs.find((e) => e.name == "intelligence_save_prof").current ? 1 : 0 }, wis: { value: sourceCharacter.attribs.find((e) => e.name == "wisdom").current, proficient: sourceCharacter.attribs.find((e) => e.name == "wisdom_save_prof").current ? 1 : 0 }, cha: { value: sourceCharacter.attribs.find((e) => e.name == "charisma").current, proficient: sourceCharacter.attribs.find((e) => e.name == "charisma_save_prof").current ? 1 : 0 } }; /* ----------------------------------------------------------------- */ /* character.data.skills..value is all that matters /* values can be 0, 0.5, 1 or 2 /* 0 = regular /* 0.5 = half-proficient /* 1 = proficient /* 2 = expertise /* foundry takes care of calculating the rest /* ----------------------------------------------------------------- */ const skills = { acr: { value: sourceCharacter.attribs.find((e) => e.name == "acrobatics_type").current }, ani: { value: sourceCharacter.attribs.find((e) => e.name == "animal_handling_type").current }, ath: { value: sourceCharacter.attribs.find((e) => e.name == "athletics_type").current }, dec: { value: sourceCharacter.attribs.find((e) => e.name == "deception_type").current }, ins: { value: sourceCharacter.attribs.find((e) => e.name == "insight_type").current }, inv: { value: sourceCharacter.attribs.find((e) => e.name == "investigation_type").current }, itm: { value: sourceCharacter.attribs.find((e) => e.name == "intimidation_type").current }, lor: { value: sourceCharacter.attribs.find((e) => e.name == "lore_type").current }, med: { value: sourceCharacter.attribs.find((e) => e.name == "medicine_type").current }, nat: { value: sourceCharacter.attribs.find((e) => e.name == "nature_type").current }, per: { value: sourceCharacter.attribs.find((e) => e.name == "persuasion_type").current }, pil: { value: sourceCharacter.attribs.find((e) => e.name == "piloting_type").current }, prc: { value: sourceCharacter.attribs.find((e) => e.name == "perception_type").current }, prf: { value: sourceCharacter.attribs.find((e) => e.name == "performance_type").current }, slt: { value: sourceCharacter.attribs.find((e) => e.name == "sleight_of_hand_type").current }, ste: { value: sourceCharacter.attribs.find((e) => e.name == "stealth_type").current }, sur: { value: sourceCharacter.attribs.find((e) => e.name == "survival_type").current }, tec: { value: sourceCharacter.attribs.find((e) => e.name == "technology_type").current } }; const targetCharacter = { name: sourceCharacter.name, type: "character", data: { abilities: abilities, details: details, skills: skills, attributes: { hp: hp } } }; let actor = await Actor.create(targetCharacter); CharacterImporter.addProfessions(sourceCharacter, actor); } // Parse all classes and add them to already created actor. // "class" is a reserved word, therefore I use profession where I can. static async addProfessions(sourceCharacter, actor) { let result = []; // parse all class and multiclassX items // couldn't get Array.filter to work here for some reason // result = array of objects. each object is a separate class sourceCharacter.attribs.forEach((e) => { if (CharacterImporter.classOrMulticlass(e.name)) { var t = { profession: CharacterImporter.capitalize(e.current), type: CharacterImporter.baseOrMulti(e.name), level: CharacterImporter.getLevel(e, sourceCharacter) }; result.push(t); } }); // pull classes directly from system compendium and add them to current actor const professionsPack = await game.packs.get("sw5e.classes").getDocuments(); result.forEach((prof) => { let assignedProfession = professionsPack.find((o) => o.name === prof.profession); assignedProfession.data.data.levels = prof.level; actor.createEmbeddedDocuments("Item", [assignedProfession.data], {displaySheet: false}); }); this.addSpecies(sourceCharacter.attribs.find((e) => e.name == "race").current, actor); this.addPowers( sourceCharacter.attribs .filter((e) => e.name.search(/repeating_power.+_powername/g) != -1) .map((e) => e.current), actor ); const discoveredItems = sourceCharacter.attribs.filter( (e) => e.name.search(/repeating_inventory.+_itemname/g) != -1 ); const items = discoveredItems.map((item) => { const id = item.name.match(/-\w{19}/g); return { name: item.current, quantity: sourceCharacter.attribs.find((e) => e.name === `repeating_inventory_${id}_itemcount`).current }; }); this.addItems(items, actor); } static async addClasses(profession, level, actor) { let classes = await game.packs.get("sw5e.classes").getDocuments(); let assignedClass = classes.find((c) => c.name === profession); assignedClass.data.data.levels = level; await actor.createEmbeddedDocuments("Item", [assignedClass.data], {displaySheet: false}); } static classOrMulticlass(name) { return name === "class" || (name.includes("multiclass") && name.length <= 12); } static baseOrMulti(name) { if (name === "class") { return "base_class"; } else { return "multi_class"; } } static getLevel(item, sourceCharacter) { if (item.name === "class") { let result = sourceCharacter.attribs.find((e) => e.name === "base_level").current; return parseInt(result); } else { let result = sourceCharacter.attribs.find((e) => e.name === `${item.name}_lvl`).current; return parseInt(result); } } static capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } static async addSpecies(race, actor) { const species = await game.packs.get("sw5e.species").getDocuments(); const assignedSpecies = species.find((c) => c.name === race); const activeEffects = [...assignedSpecies.data.effects][0].data.changes; const actorData = {data: {abilities: {...actor.data.data.abilities}}}; activeEffects.map((effect) => { switch (effect.key) { case "data.abilities.str.value": actorData.data.abilities.str.value -= effect.value; break; case "data.abilities.dex.value": actorData.data.abilities.dex.value -= effect.value; break; case "data.abilities.con.value": actorData.data.abilities.con.value -= effect.value; break; case "data.abilities.int.value": actorData.data.abilities.int.value -= effect.value; break; case "data.abilities.wis.value": actorData.data.abilities.wis.value -= effect.value; break; case "data.abilities.cha.value": actorData.data.abilities.cha.value -= effect.value; break; default: break; } }); actor.update(actorData); await actor.createEmbeddedDocuments("Item", [assignedSpecies.data], {displaySheet: false}); } static async addPowers(powers, actor) { const forcePowers = await game.packs.get("sw5e.forcepowers").getDocuments(); const techPowers = await game.packs.get("sw5e.techpowers").getDocuments(); for (const power of powers) { const createdPower = forcePowers.find((c) => c.name === power) || techPowers.find((c) => c.name === power); if (createdPower) { await actor.createEmbeddedDocuments("Item", [createdPower.data], {displaySheet: false}); } } } static async addItems(items, actor) { const weapons = await game.packs.get("sw5e.weapons").getDocuments(); const armors = await game.packs.get("sw5e.armor").getDocuments(); const adventuringGear = await game.packs.get("sw5e.adventuringgear").getDocuments(); for (const item of items) { const createdItem = weapons.find((c) => c.name.toLowerCase() === item.name.toLowerCase()) || armors.find((c) => c.name.toLowerCase() === item.name.toLowerCase()) || adventuringGear.find((c) => c.name.toLowerCase() === item.name.toLowerCase()); if (createdItem) { if (item.quantity != 1) { createdItem.data.data.quantity = item.quantity; } await actor.createEmbeddedDocuments("Item", [createdItem.data], {displaySheet: false}); } } } static addImportButton(html) { const actionButtons = html.find(".header-actions"); actionButtons[0].insertAdjacentHTML( "afterend", `
` ); let characterImportButton = $(".cs-import-button"); characterImportButton.click(() => { let content = `

Saved Character JSON Import


`; let importDialog = new Dialog({ title: "Import Character from SW5e.com", content: content, buttons: { Import: { icon: ``, label: "Import Character", callback: () => { let characterData = $("#character-json").val(); console.log("Parsing Character JSON"); CharacterImporter.transform(characterData); } }, Cancel: { icon: ``, label: "Cancel", callback: () => {} } } }); importDialog.render(true); }); } }