diff --git a/src/main.mjs b/src/main.mjs index 9776ffe3..75bcc926 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -27,6 +27,8 @@ import {CultureDataModel} from "./module/data/culture.mjs"; import {CultureSheet} from "./module/sheets/CultureSheet.mjs"; import {SpeciesSheet} from "./module/sheets/SpeciesSheet.mjs"; import {ProfessionSheet} from "./module/sheets/ProfessionSheet.mjs"; +import {XmlImport} from "./module/xml-import/xml-import.mjs"; +import {XmlImportDialog} from "./module/dialog/xmlImportDialog.mjs"; async function preloadHandlebarsTemplates() { return foundry.applications.handlebars.loadTemplates([ @@ -219,6 +221,20 @@ Hooks.once("ready", async function () { }); }); + +Hooks.on("getActorContextOptions", (application, menuItems) => { + menuItems.push({ + name: "Import from XML", + icon: '', + callback: (li) => { + const actorId = li.getAttribute("data-entry-id") + const actor = game.actors.get(actorId) + //actor.import() + new XmlImportDialog(actor).render(true) + } + }) +}) + async function createTalentMacro(data, slot) { console.log(data, slot) if (data.type !== "Item") return; diff --git a/src/module/dialog/xmlImportDialog.mjs b/src/module/dialog/xmlImportDialog.mjs new file mode 100644 index 00000000..f4ac9550 --- /dev/null +++ b/src/module/dialog/xmlImportDialog.mjs @@ -0,0 +1,88 @@ +import {XmlImport} from "../xml-import/xml-import.mjs"; + +const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api + +export class XmlImportDialog extends HandlebarsApplicationMixin(ApplicationV2) { + + static DEFAULT_OPTIONS = { + classes: ['dsa41', 'dialog', 'xmlimport'], + tag: "form", + position: { + width: 320, + height: 478 + }, + window: { + resizable: false, + }, + form: { + submitOnChange: false, + closeOnSubmit: true, + handler: XmlImportDialog.#onSubmitForm + }, + } + + static PARTS = { + form: { + template: 'systems/DSA_4-1/templates/dialog/xml-import.hbs', + } + } + + /** + * @type {Actor} + * @private + */ + _actor = null + /** + * @type {XmlImport} + * @private + */ + _xmlImport = null + + constructor(actor) { + super(); + this._actor = actor + this._xmlImport = new XmlImport() + } + + static async #onSubmitForm(event, form, formData) { + event.preventDefault() + console.log("lets go", formData.object) + + const options = {} + + formData.object.importOption.forEach(p => { + options[p] = true + }) + + const file = form.querySelector('input[type="file"]').files[0] + + try { + form.querySelector('button[type="submit"]').setAttribute("disabled", true) + await this._xmlImport.importCharacter(this._actor._id, file, options) + form.querySelector('button[type="submit"]').setAttribute("disabled", false) + return true + } catch (e) { + console.error(e) + form.querySelector('button[type="submit"]').setAttribute("disabled", false) + return false + } + } + + _configureRenderOptions(options) { + super._configureRenderOptions(options) + options.window.title = `${this._actor.name} importieren` + return options + } + + async _prepareContext(options) { + const context = await super._prepareContext(options) + context.options = XmlImport.getOptions() + return context + } + + _onRender(context, options) { + + } + + +} \ No newline at end of file diff --git a/src/module/documents/character.mjs b/src/module/documents/character.mjs index 00bb83db..692efdcd 100644 --- a/src/module/documents/character.mjs +++ b/src/module/documents/character.mjs @@ -1,10 +1,10 @@ -import {importCharacter} from "../xml-import/xml-import.mjs"; import {LiturgyData} from "../data/miracle/liturgydata.mjs"; import {Zonenruestung, Zonenwunde, Wunde} from "../data/Trefferzone.js"; import {PlayerCharacterDataModel} from "../data/character.mjs"; export class Character extends Actor { + /** import() { let input = document.createElement('input') input.type = 'file' @@ -13,7 +13,7 @@ export class Character extends Actor { importCharacter(this.id, e.target.files[0]) } input.click() - } + }*/ /** * @override diff --git a/src/module/xml-import/xml-import.mjs b/src/module/xml-import/xml-import.mjs index 3427a1d2..174a432e 100644 --- a/src/module/xml-import/xml-import.mjs +++ b/src/module/xml-import/xml-import.mjs @@ -4,555 +4,575 @@ import {Profession} from "../documents/profession.mjs"; import {Culture} from "../documents/culture.mjs"; import {Species} from "../documents/species.mjs"; -let months = [ - "Praios", - "Rondra", - "Efferd", - "Travia", - "Boron", - "Hesinde", - "Firun", - "Tsa", - "Phex", - "Peraine", - "Ingerimm", - "Rahja", - "Namenloser Tag" -] +export class XmlImport { + #months = [ + "Praios", + "Rondra", + "Efferd", + "Travia", + "Boron", + "Hesinde", + "Firun", + "Tsa", + "Phex", + "Peraine", + "Ingerimm", + "Rahja", + "Namenloser Tag" + ] -/** - * @typedef ImportOptions - * @property {Boolean} skipSpecies - * @property {Boolean} skipCulture - * @property {Boolean} skipProfessions - * @property {Boolean} skipAdvantages - * @property {Boolean} skipSpecialAbilities - * @property {Boolean} skipEquipment - * @property {Boolean} skipSpells - * @property {Boolean} skipLiturgies - * @property {Boolean} skipSkills - **/ + /** + * @typedef ImportOptions + * @property {Boolean} skipMeta + * @property {Boolean} skipSpecies + * @property {Boolean} skipCulture + * @property {Boolean} skipProfessions + * @property {Boolean} skipAttributes + * @property {Boolean} skipAdvantages + * @property {Boolean} skipSpecialAbilities + * @property {Boolean} skipEquipment + * @property {Boolean} skipSpells + * @property {Boolean} skipLiturgies + * @property {Boolean} skipSkills + **/ -/** - * Imports a character from a file created in the tool Helden-Software - * @param actorId the actor-id of the character - * @param file the file from which the character should be imported - * @param {ImportOptions?} options the set of item types the import should skip - */ -export async function importCharacter(actorId, file, options = undefined) { - let actor = game.actors.get(actorId) - let xmlString = await parseFileToString(file) - let domParser = new DOMParser() - let dom = domParser.parseFromString(xmlString, 'application/xml') - - let rawJson = getJsonFromXML(dom) - - if (!options) { - options = { - skipSpecies: false, - skipCulture: false, - skipProfessions: false, - skipAdvantages: false, - skipSpecialAbilities: false, - skipEquipment: false, - skipSkills: false, - skipSpells: false, - skipLiturgies: false + static getOptions() { + return { + skipMeta: "Meta", + skipSpecies: "Rasse", + skipCulture: "Kultur", + skipProfessions: "Professionen", + skipAttribution: "Eigenschaften", + skipAdvantages: "Vor und Nachteile", + skipSpecialAbilities: "Sonderfertigkeiten", + skipEquipment: "Inventar", + skipSpells: "Zauber", + skipLiturgies: "Liturgien", + skipSkills: "Talente" } } - let characterJson = mapRawJson(actor, rawJson, options) + /** + * Imports a character from a file created in the tool Helden-Software + * @param actorId the actor-id of the character + * @param file the file from which the character should be imported + * @param {ImportOptions?} options the set of item types the import should skip + */ + async importCharacter(actorId, file, options = undefined) { + let actor = game.actors.get(actorId) + console.log("starting import with following options", options) + let xmlString = await this.#parseFileToString(file) + let domParser = new DOMParser() + let dom = domParser.parseFromString(xmlString, 'application/xml') - actor.update(characterJson) -} + let rawJson = this.#getJsonFromXML(dom) -/** - * - * @param dom the XML-Dom from which the json should be extracted - * @returns {{}} the json parsed from the xml - */ -function getJsonFromXML(dom) { - let children = [...dom.children]; - - // initializing object to be returned. - let jsonResult = {}; - - let attributes = dom.attributes ? dom.attributes : [] - for (let attribute of attributes) { - jsonResult[attribute.name] = attribute.value - } - - if (children.length) { - for (let child of children) { - - // checking is child has siblings of same name. - let childIsArray = children.filter(eachChild => eachChild.nodeName === child.nodeName).length > 1; - - // if child is array, save the values as array, else as strings. - if (childIsArray) { - if (jsonResult[child.nodeName] === undefined) { - jsonResult[child.nodeName] = [getJsonFromXML(child)]; - } else { - jsonResult[child.nodeName].push(getJsonFromXML(child)); - } - } else { - jsonResult[child.nodeName] = getJsonFromXML(child); + if (!options) { + options = { + skipMeta: false, + skipSpecies: false, + skipCulture: false, + skipProfessions: false, + skipAttributes: false, + skipAdvantages: false, + skipSpecialAbilities: false, + skipEquipment: false, + skipSkills: false, + skipSpells: false, + skipLiturgies: false } } + + let characterJson = this.#mapRawJson(actor, rawJson, options) + + actor.update(characterJson) } - return jsonResult; -} -/** - * gets the text content of a file - * @param file the file with the desired content - * @returns {Promise} a promise that returns the string contents of the file - */ -async function parseFileToString(file) { - return new Promise((resolve, reject) => { - let reader = new FileReader() - reader.readAsText(file, "utf-8") - reader.onload = event => { - resolve(event.target.result) + /** + * + * @param dom the XML-Dom from which the json should be extracted + * @returns {{}} the json parsed from the xml + */ + #getJsonFromXML(dom) { + let children = [...dom.children]; + + // initializing object to be returned. + let jsonResult = {}; + + let attributes = dom.attributes ? dom.attributes : [] + for (let attribute of attributes) { + jsonResult[attribute.name] = attribute.value } - reader.onerror = event => { - reject(event) - } - }) -} -/** - *Calculates a Birthdate String in the Calendar of Bosparans Fall - * @param json json with the day, the month and the year of the birthday - * @returns {string} a string in the format of "DD.MMMM.YYYY BF" - */ -function calculateBirthdate(json) { - let day = json.gbtag - let month = months[parseInt(json.gbmonat) - 1] - let year = json.gbjahr + if (children.length) { + for (let child of children) { - return `${day}. ${month} ${year} BF` -} + // checking is child has siblings of same name. + let childIsArray = children.filter(eachChild => eachChild.nodeName === child.nodeName).length > 1; - -async function addSkillFromCompendiumByNameToActor(talentName, taw, actor, combatStatistics, attributes) { - const compendiumOfSkills = game.packs.get('DSA_4-1.talente'); - const talentId = compendiumOfSkills.index.find(skill => skill.name === talentName) - if (talentId) { - - const talent = await compendiumOfSkills.getDocument(talentId._id); - - try { - const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [talent]))[0] - if (talent.system.gruppe === "Kampf") { - const atbasis = attributes.find(p => p.name === "at").value - const pabasis = attributes.find(p => p.name === "pa").value - const combatStatistic = combatStatistics.find(p => p.name === talent.name) - if (combatStatistic) { // melee with AT/PA values - let at = combatStatistic.at - atbasis ?? 0 - let pa = combatStatistic.pa - pabasis ?? 0 - console.log({system: {taw, at, pa}}) - embeddedDocument.update({system: {taw, at, pa}}); - } else { // ranged with only AT values which is equal to taw - embeddedDocument.update({system: {taw: taw, at: taw, pa: null}}); // at is already at raw taw and wasn't influenced by helden-software precalculations - } - } else { - embeddedDocument.update({system: {taw: taw, at: null, pa: null}}); // just regular talent with taw - } - } catch (error) { - console.error(`${talentName} not found in items`, error) - } - } -} - -async function addAdvantageFromCompendiumByNameToActor(advantageName, advantageValue, actor) { - const compendiumOfAdvantages = game.packs.get('DSA_4-1.Advantage'); - const advantageId = compendiumOfAdvantages.index.find(skill => skill.name === advantageName) - if (advantageId) { - - const advantage = await compendiumOfAdvantages.getDocument(advantageId._id); - - try { - const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [advantage]))[0] - embeddedDocument.update({system: {value: advantageValue}}); - } catch (error) { - console.error(`${advantageName} not found in items`, error) - } - } -} - -async function addSpellsFromCompendiumByNameToActor(spellName, zfw, representation, hauszauber, actor) { - - const compendiumOfSpells = game.packs.get('DSA_4-1.spells'); - const SCREAMING_NAME = spellName.toUpperCase() - const spellId = compendiumOfSpells.index.find(spell => spell.name === SCREAMING_NAME) - if (spellId) { - - const spell = await compendiumOfSpells.getDocument(spellId._id); - - try { - const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [spell]))[0] - embeddedDocument.update({system: {zfw: zfw, hauszauber: hauszauber, repräsentation: representation}}); - } catch (error) { - console.error(`${spell} not found in items`, error) - } - } -} - -async function addLiturgiesFromCompendiumByNameToActor(liturgyName, actor) { - const compendiumOfLiturgies = game.packs.get('DSA_4-1.liturgien'); - const liturgyId = compendiumOfLiturgies.index.find(liturgy => { - return liturgy.name === LiturgyData.lookupAlias(liturgyName.split(" (")[0]) - }) - if (liturgyId) { - - const liturgy = await compendiumOfLiturgies.getDocument(liturgyId._id); - - try { - await actor.createEmbeddedDocuments('Item', [liturgy]) - } catch (error) { - console.error(`${liturgy} not found in items`, error) - } - } -} - -function mapSkills(actor, held, kampfwerte) { - if (actor.itemTypes["Skill"].length > 0) { - actor.itemTypes["Skill"].forEach(s => { - actor.items.get(s._id).delete() - }) - } - if (actor.itemTypes["Blessing"].length > 0) { - actor.itemTypes["Blessing"].forEach(p => { - actor.items.get(p._id).delete() - }) - } - for (let talent in held.talentliste.talent) { - talent = held.talentliste.talent[talent] - - // hook liturgy - if (talent.name.startsWith("Liturgiekenntnis")) { - - actor.createEmbeddedDocuments('Item', [ - new Blessing({ - name: talent.name, - type: "Blessing", - system: { - gottheit: new RegExp("\\((.+)\\)").exec(talent.name)[1], - wert: talent.value + // if child is array, save the values as array, else as strings. + if (childIsArray) { + if (jsonResult[child.nodeName] === undefined) { + jsonResult[child.nodeName] = [this.#getJsonFromXML(child)]; + } else { + jsonResult[child.nodeName].push(this.#getJsonFromXML(child)); } - }) - ]) - - } else { - // proceed - const eigenschaften = held.eigenschaften.eigenschaft - addSkillFromCompendiumByNameToActor(talent.name, talent.value, actor, kampfwerte, eigenschaften) - } - } -} - -function mapAdvantages(actor, held) { - if (actor.itemTypes["Advantage"].length > 0) { - actor.itemTypes["Advantage"].forEach(a => { - actor.items.get(a._id).delete() - }) - } - for (let advantage in held.vt.vorteil) { - advantage = held.vt.vorteil[advantage] - addAdvantageFromCompendiumByNameToActor(advantage.name, advantage.value, actor) - } -} - -function mapSpells(actor, held) { - if (actor.itemTypes["Spell"].length > 0) { - actor.itemTypes["Spell"].forEach(s => { - actor.items.get(s._id).delete() - }) - } - for (let spell in held.zauberliste.zauber) { - spell = held.zauberliste.zauber[spell] - addSpellsFromCompendiumByNameToActor(spell.name, spell.value, spell.repraesentation, spell.hauszauber === "true", actor) - } -} - -function mapLiturgies(actor, liturgies) { - if (actor.itemTypes["Liturgy"].length > 0) { - actor.itemTypes["Liturgy"].forEach(l => { - actor.items.get(l._id).delete() - }) - } - for (let liturgy in liturgies) { - liturgy = liturgies[liturgy] - addLiturgiesFromCompendiumByNameToActor(liturgy.name, actor) - } -} - -async function mapProfessions(actor, professions) { - if (actor.itemTypes["Profession"].length > 0) { - actor.itemTypes["Profession"].forEach(p => { - actor.items.get(p._id).delete() - }) - } - if (professions.string) { - professions = {hauptprofession: professions} - } - Object.values(professions).forEach(profession => { - actor.createEmbeddedDocuments('Item', [ - new Profession({ - name: profession.string, - type: "Profession", - system: { - description: "", - alias: profession.tarnidentitaet ? profession.tarnidentitaet : profession.string, - revealed: !profession.tarnidentitaet + } else { + jsonResult[child.nodeName] = this.#getJsonFromXML(child); } - }) - ]) - }) - // actor.update({"system.meta.professions": professions}) -} - -async function mapSpezies(actor, spezies) { - if (actor.itemTypes["Species"].length > 0) { - actor.itemTypes["Species"].forEach(s => { - actor.items.get(s._id).delete() - }) - } - const compendiumOfSpecies = game.packs.get('DSA_4-1.spezien'); - const speciesId = compendiumOfSpecies?.index.find(species => species.name === spezies.name) - - if (speciesId) { - const species = - await compendiumOfSpecies.getDocument(speciesId); - try { - await actor.createEmbeddedDocuments('Item', [species]) - } catch (e) { - } - } else { - actor.createEmbeddedDocuments('Item', [ - new Species({ - name: spezies.name, - type: "Species", - system: { - feminineDemonym: spezies.string, - masculineDemonym: spezies.string, - description: "Importiert", - } - }) - ]) - } - -} - -async function mapKultur(actor, kultur) { - if (actor.itemTypes["Culture"].length > 0) { - actor.itemTypes["Culture"].forEach(c => { - actor.items.get(c._id).delete() - }) - } - const compendiumOfCultures = game.packs.get('DSA_4-1.kulturen'); - const cultureId = compendiumOfCultures?.index.find(culture => culture.name === kultur.name) - - if (cultureId) { - const culture = - await compendiumOfCultures.getDocument(cultureId); - try { - await actor.createEmbeddedDocuments('Item', [culture]) - } catch (e) { - } - } else { - actor.createEmbeddedDocuments('Item', [ - new Culture({ - name: kultur.name, - type: "Culture", - system: { - variant: kultur.variante.name ?? "", - feminineDemonym: kultur.string, - masculineDemonym: kultur.string, - description: "Importiert", - } - }) - ]) - } - -} - -/** - * parses a json into a fitting character-json - * @param {Character} actor - * @param rawJson the json parsed from the Helden-Software XML - * @param {ImportOptions} options - * @returns {{}} a json representation of the character - */ -function mapRawJson(actor, rawJson, options) { - let json = {} - let held = rawJson.helden.held; - json.name = held.name - json.meta = {} - if (!options.skipSpecies) mapSpezies(actor, held.basis.rasse) // as string includes the demonymized form - if (!options.skipCulture) mapKultur(actor, held.basis.kultur) // as string includes the demonymized form - if (!options.skipProfessions) mapProfessions(actor, held.basis.ausbildungen.ausbildung) - json.meta.geschlecht = held.basis.geschlecht.name - json.meta.haarfarbe = held.basis.rasse.aussehen.haarfarbe - json.meta.groesse = held.basis.rasse.groesse.value - json.meta.augenfarbe = held.basis.rasse.aussehen.augenfarbe - json.meta.geburtstag = calculateBirthdate(held.basis.rasse.aussehen) - json.meta.alter = held.basis.rasse.aussehen.alter - json.meta.gewicht = held.basis.rasse.groesse.gewicht - json.meta.aussehen = [ - held.basis.rasse.aussehen.aussehentext0, - held.basis.rasse.aussehen.aussehentext1, - held.basis.rasse.aussehen.aussehentext2, - held.basis.rasse.aussehen.aussehentext3].join('\n') - json.meta.familie = [ - held.basis.rasse.aussehen.familietext0, - held.basis.rasse.aussehen.familietext1, - held.basis.rasse.aussehen.familietext2, - held.basis.rasse.aussehen.familietext3, - held.basis.rasse.aussehen.familietext4, - held.basis.rasse.aussehen.familietext5].join('\n') - json.meta.titel = held.basis.rasse.aussehen.titel - json.meta.stand = held.basis.rasse.aussehen.stand - let attributes = held.eigenschaften.eigenschaft - json.attribute = {} - if (held.basis.gilde) { - json.attribute.gilde = held.basis.gilde.name - } - json.attribute.mu = getAttributeJson(attributes, "Mut") - json.attribute.kl = getAttributeJson(attributes, "Klugheit") - json.attribute.in = getAttributeJson(attributes, "Intuition") - json.attribute.ch = getAttributeJson(attributes, "Charisma") - json.attribute.ff = getAttributeJson(attributes, "Fingerfertigkeit") - json.attribute.ge = getAttributeJson(attributes, "Gewandtheit") - json.attribute.ko = getAttributeJson(attributes, "Konstitution") - json.attribute.kk = getAttributeJson(attributes, "Körperkraft") - json.mr = { - mod: filterAttribute(attributes, "Magieresistenz").mod - } - json.lep = { - mod: filterAttribute(attributes, "Lebensenergie").mod - } - json.aup = { - mod: filterAttribute(attributes, "Ausdauer").mod - } - json.asp = { - mod: filterAttribute(attributes, "Astralenergie").mod - } - json.kap = { - mod: filterAttribute(attributes, "Karmaenergie").mod - } - let attribute = filterAttribute(attributes, "Karmaenergie") - attribute = filterAttribute(attributes, "ini") - json.ini = { - mod: attribute.mod, - aktuell: attribute.value - } - json.attribute.so = getAttributeJson(attributes, "Sozialstatus") - if (!options.skipAdvantages) mapAdvantages(actor, held) - let specialAbilities = [] - let liturgies = [] - for (let specialAbility in held.sf.sonderfertigkeit) { - specialAbility = held.sf.sonderfertigkeit[specialAbility] - if (specialAbility.name.startsWith("Liturgie:")) { - liturgies.push({ - name: specialAbility.name.replace("Liturgie:", "").trim(), - }) - } else { - let specialAbilityJson = { - name: specialAbility.name, - auswahlen: [] } - let fields = Object.keys(specialAbility) - if (fields.length > 1) { - for (let field in fields) { - field = fields[field] - if (field !== "name") { - let choices = specialAbility[field] - if (choices.hasOwnProperty("name")) { - specialAbilityJson.auswahlen.push(choices.name) - } else { - for (let choice in choices.wahl) { - choice = choices.wahl[choice] - specialAbilityJson.auswahlen.push(choice.value) + } + + return jsonResult; + } + + /** + * gets the text content of a file + * @param file the file with the desired content + * @returns {Promise} a promise that returns the string contents of the file + */ + async #parseFileToString(file) { + return new Promise((resolve, reject) => { + let reader = new FileReader() + reader.readAsText(file, "utf-8") + reader.onload = event => { + resolve(event.target.result) + } + reader.onerror = event => { + reject(event) + } + }) + } + + + /** + *Calculates a Birthdate String in the Calendar of Bosparans Fall + * @param json json with the day, the month and the year of the birthday + * @returns {string} a string in the format of "DD.MMMM.YYYY BF" + */ + #calculateBirthdate(json) { + let day = json.gbtag + let month = this.#months[parseInt(json.gbmonat) - 1] + let year = json.gbjahr + + return `${day}. ${month} ${year} BF` + } + + + /** + * parses a json into a fitting character-json + * @param {Character} actor + * @param rawJson the json parsed from the Helden-Software XML + * @param {ImportOptions} options + * @returns {{}} a json representation of the character + */ + #mapRawJson(actor, rawJson, options) { + let json = {} + let held = rawJson.helden.held; + json.name = held.name + json.meta = {} + if (!options.skipSpecies) this.#mapSpezies(actor, held.basis.rasse) // as string includes the demonymized form + if (!options.skipCulture) this.#mapKultur(actor, held.basis.kultur) // as string includes the demonymized form + if (!options.skipProfessions) this.#mapProfessions(actor, held.basis.ausbildungen.ausbildung) + if (!options.skipMeta) { + json.meta.geschlecht = held.basis.geschlecht.name + json.meta.haarfarbe = held.basis.rasse.aussehen.haarfarbe + json.meta.groesse = held.basis.rasse.groesse.value + json.meta.augenfarbe = held.basis.rasse.aussehen.augenfarbe + json.meta.geburtstag = this.#calculateBirthdate(held.basis.rasse.aussehen) + json.meta.alter = held.basis.rasse.aussehen.alter + json.meta.gewicht = held.basis.rasse.groesse.gewicht + json.meta.aussehen = [ + held.basis.rasse.aussehen.aussehentext0, + held.basis.rasse.aussehen.aussehentext1, + held.basis.rasse.aussehen.aussehentext2, + held.basis.rasse.aussehen.aussehentext3].join('\n') + json.meta.familie = [ + held.basis.rasse.aussehen.familietext0, + held.basis.rasse.aussehen.familietext1, + held.basis.rasse.aussehen.familietext2, + held.basis.rasse.aussehen.familietext3, + held.basis.rasse.aussehen.familietext4, + held.basis.rasse.aussehen.familietext5].join('\n') + json.meta.titel = held.basis.rasse.aussehen.titel + json.meta.stand = held.basis.rasse.aussehen.stand + } + let attributes = held.eigenschaften.eigenschaft + json.attribute = {} + if (!options.skipAttributes) { + if (held.basis.gilde) { + json.attribute.gilde = held.basis.gilde.name + } + json.attribute.mu = this.#getAttributeJson(attributes, "Mut") + json.attribute.kl = this.#getAttributeJson(attributes, "Klugheit") + json.attribute.in = this.#getAttributeJson(attributes, "Intuition") + json.attribute.ch = this.#getAttributeJson(attributes, "Charisma") + json.attribute.ff = this.#getAttributeJson(attributes, "Fingerfertigkeit") + json.attribute.ge = this.#getAttributeJson(attributes, "Gewandtheit") + json.attribute.ko = this.#getAttributeJson(attributes, "Konstitution") + json.attribute.kk = this.#getAttributeJson(attributes, "Körperkraft") + json.mr = { + mod: this.#filterAttribute(attributes, "Magieresistenz").mod + } + json.lep = { + mod: this.#filterAttribute(attributes, "Lebensenergie").mod + } + json.aup = { + mod: this.#filterAttribute(attributes, "Ausdauer").mod + } + json.asp = { + mod: this.#filterAttribute(attributes, "Astralenergie").mod + } + json.kap = { + mod: this.#filterAttribute(attributes, "Karmaenergie").mod + } + let attribute = this.#filterAttribute(attributes, "ini") + json.ini = { + mod: attribute.mod, + aktuell: attribute.value + } + json.attribute.so = this.#getAttributeJson(attributes, "Sozialstatus") + } + + if (!options.skipAdvantages) this.#mapAdvantages(actor, held) + let specialAbilities = [] + let liturgies = [] + for (let specialAbility in held.sf.sonderfertigkeit) { + specialAbility = held.sf.sonderfertigkeit[specialAbility] + if (specialAbility.name.startsWith("Liturgie:")) { + if (!options.skipLiturgies) { + liturgies.push({ + name: specialAbility.name.replace("Liturgie:", "").trim(), + }) + } + } else { + if (!options.skipSpecialAbilities) { + let specialAbilityJson = { + name: specialAbility.name, + auswahlen: [] + } + let fields = Object.keys(specialAbility) + if (fields.length > 1) { + for (let field in fields) { + field = fields[field] + if (field !== "name") { + let choices = specialAbility[field] + if (choices.hasOwnProperty("name")) { + specialAbilityJson.auswahlen.push(choices.name) + } else { + for (let choice in choices.wahl) { + choice = choices.wahl[choice] + specialAbilityJson.auswahlen.push(choice.value) + } + } } } } + specialAbilities.push(specialAbilityJson) } } - specialAbilities.push(specialAbilityJson) } - } - json.sonderfertigkeiten = specialAbilities - json.liturgien = liturgies + json.sonderfertigkeiten = specialAbilities + json.liturgien = liturgies - let combatValues = [] - for (let combatValue in held.kampf.kampfwerte) { - combatValue = held.kampf.kampfwerte[combatValue] - combatValues.push({ - name: combatValue.name, - at: combatValue.attacke.value, - pa: combatValue.parade.value, - }) - } - json.kampfwerte = combatValues - - if (!options.skipSkills) mapSkills(actor, held, combatValues) - if (!options.skipSpells) mapSpells(actor, held) - if (!options.skipLiturgies) mapLiturgies(actor, liturgies) - - let notes = [] - for (let note in held.kommentare) { - note = held.kommentare[note] - if (note.hasOwnProperty("key")) { - notes.push({ - key: note.key, - notiz: note.kommentar, + let combatValues = [] + for (let combatValue in held.kampf.kampfwerte) { + combatValue = held.kampf.kampfwerte[combatValue] + combatValues.push({ + name: combatValue.name, + at: combatValue.attacke.value, + pa: combatValue.parade.value, }) } - } - json.notizen = notes + json.kampfwerte = combatValues - return { - name: held.name, - system: json, - } -} + if (!options.skipSkills) this.#mapSkills(actor, held, combatValues) + if (!options.skipSpells) this.#mapSpells(actor, held) + if (!options.skipLiturgies) this.#mapLiturgies(actor, liturgies) -/** - * - * @param attributes an array with the attributes - * @param name the name of the chosen attribute - * @returns {{}} a representation of the chosen Attribute - */ -function getAttributeJson(attributes, name) { - let attribute = filterAttribute(attributes, name) - return { - start: parseInt(attribute.startwert), - aktuell: parseInt(attribute.value), - mod: parseInt(attribute.mod), - } -} - -/** - * filters a given attribute array based on the name of an attribute - * @param attributes the attribute array - * @param name the name of the desired attribute - * @returns {{}} the json of the desired attribute - */ -function filterAttribute(attributes, name) { - return attributes.filter(attribute => attribute.name === name)[0] -} - -Hooks.on("getActorContextOptions", (application, menuItems) => { - menuItems.push({ - name: "Import from XML", - icon: '', - callback: (li) => { - const actorId = li.getAttribute("data-entry-id") - const actor = game.actors.get(actorId) - actor.import() + let notes = [] + for (let note in held.kommentare) { + note = held.kommentare[note] + if (note.hasOwnProperty("key")) { + notes.push({ + key: note.key, + notiz: note.kommentar, + }) + } } - }) -}) + json.notizen = notes + + return { + name: held.name, + system: json, + } + } + + + /** + * @param attributes an array with the attributes + * @param name the name of the chosen attribute + * @returns {{}} a representation of the chosen Attribute + */ + #getAttributeJson(attributes, name) { + let attribute = this.#filterAttribute(attributes, name) + return { + start: parseInt(attribute.startwert), + aktuell: parseInt(attribute.value), + mod: parseInt(attribute.mod), + } + } + + /** + * filters a given attribute array based on the name of an attribute + * @param attributes the attribute array + * @param name the name of the desired attribute + * @returns {{}} the json of the desired attribute + */ + #filterAttribute(attributes, name) { + return attributes.filter(attribute => attribute.name === name)[0] + } + + async #addSkillFromCompendiumByNameToActor(talentName, taw, actor, combatStatistics, attributes) { + const compendiumOfSkills = game.packs.get('DSA_4-1.talente'); + const talentId = compendiumOfSkills.index.find(skill => skill.name === talentName) + if (talentId) { + + const talent = await compendiumOfSkills.getDocument(talentId._id); + + try { + const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [talent]))[0] + if (talent.system.gruppe === "Kampf") { + const atbasis = attributes.find(p => p.name === "at").value + const pabasis = attributes.find(p => p.name === "pa").value + const combatStatistic = combatStatistics.find(p => p.name === talent.name) + if (combatStatistic) { // melee with AT/PA values + let at = combatStatistic.at - atbasis ?? 0 + let pa = combatStatistic.pa - pabasis ?? 0 + console.log({system: {taw, at, pa}}) + embeddedDocument.update({system: {taw, at, pa}}); + } else { // ranged with only AT values which is equal to taw + embeddedDocument.update({system: {taw: taw, at: taw, pa: null}}); // at is already at raw taw and wasn't influenced by helden-software precalculations + } + } else { + embeddedDocument.update({system: {taw: taw, at: null, pa: null}}); // just regular talent with taw + } + } catch (error) { + console.error(`${talentName} not found in items`, error) + } + } + } + + async #addAdvantageFromCompendiumByNameToActor(advantageName, advantageValue, actor) { + const compendiumOfAdvantages = game.packs.get('DSA_4-1.Advantage'); + const advantageId = compendiumOfAdvantages.index.find(skill => skill.name === advantageName) + if (advantageId) { + + const advantage = await compendiumOfAdvantages.getDocument(advantageId._id); + + try { + const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [advantage]))[0] + embeddedDocument.update({system: {value: advantageValue}}); + } catch (error) { + console.error(`${advantageName} not found in items`, error) + } + } + } + + async #addSpellsFromCompendiumByNameToActor(spellName, zfw, representation, hauszauber, actor) { + const compendiumOfSpells = game.packs.get('DSA_4-1.spells'); + const SCREAMING_NAME = spellName.toUpperCase() + const spellId = compendiumOfSpells.index.find(spell => spell.name === SCREAMING_NAME) + if (spellId) { + + const spell = await compendiumOfSpells.getDocument(spellId._id); + + try { + const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [spell]))[0] + embeddedDocument.update({system: {zfw: zfw, hauszauber: hauszauber, repräsentation: representation}}); + } catch (error) { + console.error(`${spell} not found in items`, error) + } + } + } + + async #addLiturgiesFromCompendiumByNameToActor(liturgyName, actor) { + const compendiumOfLiturgies = game.packs.get('DSA_4-1.liturgien'); + const liturgyId = compendiumOfLiturgies.index.find(liturgy => { + return liturgy.name === LiturgyData.lookupAlias(liturgyName.split(" (")[0]) + }) + if (liturgyId) { + + const liturgy = await compendiumOfLiturgies.getDocument(liturgyId._id); + + try { + await actor.createEmbeddedDocuments('Item', [liturgy]) + } catch (error) { + console.error(`${liturgy} not found in items`, error) + } + } + } + + #mapSkills(actor, held, kampfwerte) { + if (actor.itemTypes["Skill"].length > 0) { + actor.itemTypes["Skill"].forEach(s => { + actor.items.get(s._id).delete() + }) + } + if (actor.itemTypes["Blessing"].length > 0) { + actor.itemTypes["Blessing"].forEach(p => { + actor.items.get(p._id).delete() + }) + } + for (let talent in held.talentliste.talent) { + talent = held.talentliste.talent[talent] + + // hook liturgy + if (talent.name.startsWith("Liturgiekenntnis")) { + + actor.createEmbeddedDocuments('Item', [ + new Blessing({ + name: talent.name, + type: "Blessing", + system: { + gottheit: new RegExp("\\((.+)\\)").exec(talent.name)[1], + wert: talent.value + } + }) + ]) + + } else { + // proceed + const eigenschaften = held.eigenschaften.eigenschaft + this.#addSkillFromCompendiumByNameToActor(talent.name, talent.value, actor, kampfwerte, eigenschaften) + } + } + } + + #mapAdvantages(actor, held) { + if (actor.itemTypes["Advantage"].length > 0) { + actor.itemTypes["Advantage"].forEach(a => { + actor.items.get(a._id).delete() + }) + } + for (let advantage in held.vt.vorteil) { + advantage = held.vt.vorteil[advantage] + this.#addAdvantageFromCompendiumByNameToActor(advantage.name, advantage.value, actor) + } + } + + #mapSpells(actor, held) { + if (actor.itemTypes["Spell"].length > 0) { + actor.itemTypes["Spell"].forEach(s => { + actor.items.get(s._id).delete() + }) + } + for (let spell in held.zauberliste.zauber) { + spell = held.zauberliste.zauber[spell] + this.#addSpellsFromCompendiumByNameToActor(spell.name, spell.value, spell.repraesentation, spell.hauszauber === "true", actor) + } + } + + #mapLiturgies(actor, liturgies) { + if (actor.itemTypes["Liturgy"].length > 0) { + actor.itemTypes["Liturgy"].forEach(l => { + actor.items.get(l._id).delete() + }) + } + for (let liturgy in liturgies) { + liturgy = liturgies[liturgy] + this.#addLiturgiesFromCompendiumByNameToActor(liturgy.name, actor) + } + } + + async #mapProfessions(actor, professions) { + if (actor.itemTypes["Profession"].length > 0) { + actor.itemTypes["Profession"].forEach(p => { + actor.items.get(p._id).delete() + }) + } + if (professions.string) { + professions = {hauptprofession: professions} + } + Object.values(professions).forEach(profession => { + actor.createEmbeddedDocuments('Item', [ + new Profession({ + name: profession.string, + type: "Profession", + system: { + description: "", + alias: profession.tarnidentitaet ? profession.tarnidentitaet : profession.string, + revealed: !profession.tarnidentitaet + } + }) + ]) + }) + } + + async #mapSpezies(actor, spezies) { + if (actor.itemTypes["Species"].length > 0) { + actor.itemTypes["Species"].forEach(s => { + actor.items.get(s._id).delete() + }) + } + const compendiumOfSpecies = game.packs.get('DSA_4-1.spezien'); + const speciesId = compendiumOfSpecies?.index.find(species => species.name === spezies.name) + + if (speciesId) { + const species = + await compendiumOfSpecies.getDocument(speciesId); + try { + await actor.createEmbeddedDocuments('Item', [species]) + } catch (e) { + } + } else { + actor.createEmbeddedDocuments('Item', [ + new Species({ + name: spezies.name, + type: "Species", + system: { + feminineDemonym: spezies.string, + masculineDemonym: spezies.string, + description: "Importiert", + } + }) + ]) + } + + } + + async #mapKultur(actor, kultur) { + if (actor.itemTypes["Culture"].length > 0) { + actor.itemTypes["Culture"].forEach(c => { + actor.items.get(c._id).delete() + }) + } + const compendiumOfCultures = game.packs.get('DSA_4-1.kulturen'); + const cultureId = compendiumOfCultures?.index.find(culture => culture.name === kultur.name) + + if (cultureId) { + const culture = + await compendiumOfCultures.getDocument(cultureId); + try { + await actor.createEmbeddedDocuments('Item', [culture]) + } catch (e) { + } + } else { + actor.createEmbeddedDocuments('Item', [ + new Culture({ + name: kultur.name, + type: "Culture", + system: { + variant: kultur.variante.name ?? "", + feminineDemonym: kultur.string, + masculineDemonym: kultur.string, + description: "Importiert", + } + }) + ]) + } + + } + +} diff --git a/src/style/organisms/_xml-import-dialog.scss b/src/style/organisms/_xml-import-dialog.scss new file mode 100644 index 00000000..c8f59e36 --- /dev/null +++ b/src/style/organisms/_xml-import-dialog.scss @@ -0,0 +1,71 @@ +.application.dsa41.dialog.xmlimport { + + section.window-content { + + section { + + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 32px 1fr 32px; + gap: 8px 0; + grid-template-areas: "file" "options" "actions"; + + .file-input { + grid-area: file; + display: flex; + gap: 8px; + + label { + flex: 0; + height: 32px; + line-height: 32px; + vertical-align: middle; + width: 80px; + } + + input { + flex: 1; + } + } + + fieldset { + grid-area: options; + border-left: 0; + border-bottom: 0; + border-right: 0; + + legend { + padding: 0 16px; + text-align: center; + } + + div { + + label { + + height: 21px; + + input { + line-height: 21px; + height: 21px; + } + + span { + line-height: 21px; + height: 21px; + vertical-align: 2px; + } + } + } + + } + + button { + grid-area: actions; + width: 100%; + height: 32px; + } + + } + } +} \ No newline at end of file diff --git a/src/style/styles.scss b/src/style/styles.scss index 32e2a996..f88ab0fc 100644 --- a/src/style/styles.scss +++ b/src/style/styles.scss @@ -23,4 +23,5 @@ @use "organisms/culture-sheet"; @use "organisms/species-sheet"; @use "organisms/profession-sheet"; +@use "organisms/xml-import-dialog"; diff --git a/src/templates/dialog/xml-import.hbs b/src/templates/dialog/xml-import.hbs new file mode 100644 index 00000000..c30a7b64 --- /dev/null +++ b/src/templates/dialog/xml-import.hbs @@ -0,0 +1,21 @@ +
+ +
+ + +
+ +
+ Folgendes nicht importieren + + + {{#each options}} +
+
+ {{/each}} + +
+ + + +
\ No newline at end of file