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 @@
+
\ No newline at end of file