import {LiturgyData} from "../data/miracle/liturgydata.mjs"; import {Blessing} from "../documents/blessing.mjs"; import {Profession} from "../documents/profession.mjs"; import {Culture} from "../documents/culture.mjs"; import {Species} from "../documents/species.mjs"; import {SpecialAbility} from "../documents/specialAbility.mjs"; import {Equipment} from "../documents/equipment.mjs"; export class XmlImport { #months = [ "Praios", "Rondra", "Efferd", "Travia", "Boron", "Hesinde", "Firun", "Tsa", "Phex", "Peraine", "Ingerimm", "Rahja", "Namenloser Tag" ] /** * @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 **/ 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" } } /** * 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') let rawJson = this.#getJsonFromXML(dom) 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) } /** * * @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 } 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] = [this.#getJsonFromXML(child)]; } else { jsonResult[child.nodeName].push(this.#getJsonFromXML(child)); } } else { jsonResult[child.nodeName] = this.#getJsonFromXML(child); } } } 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.replace(",", ".") // fixes localisation 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 } if (!options.skipAttributes) { let attributes = held.eigenschaften.eigenschaft json.attribute = {} 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) } } } this.#mapSpecialAbilities(actor, 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) this.#mapSkills(actor, held, combatValues) if (!options.skipSpells) this.#mapSpells(actor, held) if (!options.skipLiturgies) this.#mapLiturgies(actor, liturgies) if (!options.skipEquipment) this.#mapEquipment(actor, held) 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.value), 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.Skills'); 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 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.Liturgies'); 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) } } #mapEquipment(actor, held) { if (actor.itemTypes["Equipment"].length > 0) { actor.itemTypes["Equipment"].forEach(e => { actor.items.get(e._id).delete() }) } let i = 100 held.gegenstände?.gegenstand?.forEach(e => { const compendiumOfArmor = game.packs.get('DSA_4-1.Armor'); const compendiumOfWeapons = game.packs.get('DSA_4-1.Weapons'); const compendiumOfAmmunition = game.packs.get('DSA_4-1.Ammunition'); const compendiumOfItems = game.packs.get('DSA_4-1.Items'); let eId = null let compendium = null if (compendiumOfArmor?.index.find(p => p.name === e.name)) { eId = compendiumOfArmor?.index.find(p => p.name === e.name) compendium = compendiumOfArmor } if (compendiumOfWeapons?.index.find(p => p.name === e.name)) { eId = compendiumOfWeapons?.index.find(p => p.name === e.name) compendium = compendiumOfWeapons } if (compendiumOfAmmunition?.index.find(p => p.name === e.name)) { eId = compendiumOfAmmunition?.index.find(p => p.name === e.name) compendium = compendiumOfAmmunition } if (compendiumOfItems?.index.find(p => p.name === e.name)) { eId = compendiumOfItems?.index.find(p => p.name === e.name) compendium = compendiumOfItems } if (eId && compendium) { compendium.getDocument(eId._id).then(sf => actor.createEmbeddedDocuments('Item', [sf])) } else { actor.createEmbeddedDocuments('Item', [new Equipment( { name: e.modallgemein?.name?.value ?? e.name, type: "Equipment", sort: (i++) * 100, system: { quantity: e.anzahl, price: e.modallgemein?.preis.value, weight: e.modallgemein?.gewicht.value, } }) ]) } }) } 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 #mapSpecialAbilities(actor, specialAbilities) { if (actor.itemTypes["SpecialAbility"].length > 0) { actor.itemTypes["SpecialAbility"].forEach(s => { actor.items.get(s._id).delete() }) } specialAbilities.forEach((specialAbility) => { const compendiumOfSF = game.packs.get('DSA_4-1.sonderfertigkeiten'); const regexp = /(\w*)\W?(\w*)?/ const [variantName, baseAbility, level] = regexp.exec(specialAbility.name) const sfId = compendiumOfSF?.index.find(sf => sf.name === baseAbility) if (sfId) { if (variantName) { console.debug(baseAbility + " to be imported as " + variantName) // TODO if baseAbility already imported, adjust its level instead of adding a new embeddedDocument } compendiumOfSF.getDocument(sfId._id).then(sf => { actor.createEmbeddedDocuments('Item', [sf]).then(_ => { actor.itemTypes["SpecialAbility"].find(p => p.name === baseAbility).update({ "system.value": variantName }) }) }) } else { actor.createEmbeddedDocuments('Item', [ new SpecialAbility({ name: specialAbility.name, type: "SpecialAbility", system: { description: specialAbility.auswahl?.wahl?.join("\n"), } }) ]) } }) } 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.Species'); const speciesId = compendiumOfSpecies?.index.find(species => species.name === spezies.name)?._id 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.Cultures'); const cultureId = compendiumOfCultures?.index.find(culture => culture.name === kultur.name)?._id 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 ?? kultur.string ?? "", feminineDemonym: kultur.string, masculineDemonym: kultur.string, description: "Importiert", } }) ]) } } }