diff --git a/src/module/Actors/dsa-actor.mjs b/src/module/Actors/dsa-actor.mjs index da6bcce9..2ddd9ad9 100644 --- a/src/module/Actors/dsa-actor.mjs +++ b/src/module/Actors/dsa-actor.mjs @@ -1,3 +1,13 @@ export class DsaActor extends Actor { + import() { + let input = document.createElement('input') + input.type = 'file' + input.accept = '.xml' + input.onchange = e => { + importCharacter(this.id, e.target.files[0]) + } + input.click() + } + } \ No newline at end of file diff --git a/src/module/character/character.mjs b/src/module/character/character.mjs index a483d6d7..906a1c1a 100644 --- a/src/module/character/character.mjs +++ b/src/module/character/character.mjs @@ -10,7 +10,7 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel { meta: new SchemaField({ spezies: new StringField(), kultur: new StringField(), - profession: new StringField(), + professions: new ArrayField(new StringField()), geschlecht: new StringField(), haarfarbe: new StringField(), groesse: new NumberField({ required: true, integer: false }), @@ -18,41 +18,112 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel { geburtstag: new StringField(), alter: new NumberField({ required: true, integer: true }), gewicht: new NumberField({ required: true, integer: true }), + aussehen: new ArrayField(new StringField()), + familie: new ArrayField(new StringField()), + titel: new StringField(), + stand: new StringField(), }), attribute: new SchemaField({ - mu: new NumberField({ required: true, integer: true }), - kl: new NumberField({ required: true, integer: true }), - in: new NumberField({ required: true, integer: true }), - ch: new NumberField({ required: true, integer: true }), - ff: new NumberField({ required: true, integer: true }), - ge: new NumberField({ required: true, integer: true }), - ko: new NumberField({ required: true, integer: true }), - kk: new NumberField({ required: true, integer: true }), - mr: new NumberField({ required: true, integer: true }), - lep: new NumberField({ required: true, integer: true }), - aup: new NumberField({ required: true, integer: true }), - asp: new NumberField({ required: false, integer: true }), - kap: new NumberField({ required: false, integer: true }), - at: new NumberField({ required: true, integer: true }), - pa: new NumberField({ required: true, integer: true }), - fk: new NumberField({ required: true, integer: true }), - ini: new NumberField({ required: true, integer: true }), - so: new NumberField({ required: true, integer: true }), + mu: new SchemaField({ + start: new NumberField({ required: true, integer: true }), + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + kl: new SchemaField({ + start: new NumberField({ required: true, integer: true }), + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + in: new SchemaField({ + start: new NumberField({ required: true, integer: true }), + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + ch: new SchemaField({ + start: new NumberField({ required: true, integer: true }), + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + ff: new SchemaField({ + start: new NumberField({ required: true, integer: true }), + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + ge: new SchemaField({ + start: new NumberField({ required: true, integer: true }), + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + ko: new SchemaField({ + start: new NumberField({ required: true, integer: true }), + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + kk: new SchemaField({ + start: new NumberField({ required: true, integer: true }), + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + mr: new SchemaField({ + mod: new NumberField({ required: true, integer: true }), + }), + lep: new SchemaField({ + mod: new NumberField({ required: true, integer: true }), + }), + aup: new SchemaField({ + mod: new NumberField({ required: true, integer: true }), + }), + asp: new SchemaField({ + mod: new NumberField({ required: true, integer: true }), + }), + kap: new SchemaField({ + mod: new NumberField({ required: true, integer: true }), + }), + at: new SchemaField({ + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + pa: new SchemaField({ + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + fk: new SchemaField({ + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + ini: new SchemaField({ + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), + so: new SchemaField({ + start: new NumberField({ required: true, integer: true }), + aktuell: new NumberField({ required: true, integer: true }), + mod: new NumberField({ required: true, integer: true }), + }), gilde: new StringField(), }), + vornachteile: new ArrayField(new SchemaField({ + name: new SchemaField(), + value: new NumberField(), + })), + sonderfertigkeiten: new ArrayField(new SchemaField({ + name: new SchemaField(), + auswahlen: new ArrayField(new StringField()), + })), talente: new ArrayField(new SchemaField({ name: new StringField(), taw: new NumberField({required: true, integer: true }), - kategorie: new StringField(), probe: new StringField(), + be: new NumberField({required: false, integer: true }), + komplexitaet: new NumberField({required: false, integer: true }), })), zauber: new ArrayField(new SchemaField({ name: new StringField(), rep: new StringField(), - merkmale: new ArrayField(new StringField()), hauszauber: new BooleanField(), zfw: new NumberField({ required: true, integer: true }), - quelle: new StringField(), + anmerkungen: new StringField(), + komplexitaet: new NumberField({required: true, integer: true }), })), liturgien: new ArrayField(new SchemaField({ name: new StringField(), @@ -62,6 +133,10 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel { at: new NumberField({ required: true, integer: true }), pa: new NumberField({ required: true, integer: true }), })), + notizen: new ArrayField(new SchemaField({ + key: new StringField(), + notiz: new StringField(), + })) } } diff --git a/src/module/character/example-character.xml b/src/module/character/example-character.xml index 01738fc7..f636eace 100644 --- a/src/module/character/example-character.xml +++ b/src/module/character/example-character.xml @@ -1,6 +1,6 @@ - + @@ -13,15 +13,17 @@ - + + string="Akademie der Magischen Rüstung zu Gareth " tarnidentitaet="Depp vom Dienst"> - + @@ -73,6 +75,13 @@ + + + + + + + @@ -90,6 +99,13 @@ + + + + + + + @@ -257,6 +273,59 @@ text="Sonderfertigkeit hinzugefügt" time="1758728180962" version="HS 5.5.3"/> + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -320,8 +389,9 @@ value="6"/> - - + + + @@ -506,6 +576,7 @@ + @@ -542,10 +613,10 @@ - D/lXw741ikWhJ+dIE/eCcUgT5vw= + a1RMsniSGUvFk5vUM6faRb5HF7M= - Yo5RyWxO8N8Z0ReQlPhESaEjbpFUFTANob25mAXlFTH0eCBano63WQ== + IoH2tMVRNhVL5zF5VrhsiYRdosA0GopNsJMf4tFpYVi5yPW6RhGqNQ== diff --git a/src/module/xml-import/xml-import.mjs b/src/module/xml-import/xml-import.mjs new file mode 100644 index 00000000..cdb093b1 --- /dev/null +++ b/src/module/xml-import/xml-import.mjs @@ -0,0 +1,301 @@ +let months = [ + "Praios", + "Rondra", + "Efferd", + "Travia", + "Boron", + "Hesinde", + "Firun", + "Tsa", + "Phex", + "Peraine", + "Ingerimm", + "Rahja", + "Namenloser Tag" +] + + +/** + * 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 + */ +async function importCharacter(actorId, file) { + 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) + let characterJson = mapRawJson(rawJson) + + actor.update(characterJson) +} + +/** + * + * @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 + 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); + } + } + } + + 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) + } + reader.error = 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 + + return `${day}. ${month} ${year} BF` +} + +/** + * parses a json into a fitting character-json + * @param rawJson the json parsed from the Helden-Software XML + * @returns {{}} a json representation of the character + */ +function mapRawJson(rawJson) { + let json = {} + with (rawJson.held) { + json.name = name + json.meta = {} + json.meta.spezies = basis.rasse.string + json.meta.kultur = basis.kultur.string + let professions = [] + for (ausbildung in basis.ausbildungen) { + if (ausbildung.tarnidentitaet) { + professions = [ausbildung.tarnidentitaet] + break; + } + let ausbildungString = ausbildung.string + professions.push(ausbildungString) + } + json.meta.profession = professions + json.geschlecht = basis.geschlecht.name + json.haarfarbe = basis.rasse.aussehen.haarfarbe + json.groesse = basis.rasse.groesse.value + json.augenfarbe = basis.rasse.aussehen.augenfarbe + json.geburtstag = calculateBirthdate(basis.rasse.aussehen) + json.alter = basis.rasse.aussehen.alter + json.gewicht = basis.rasse.groesse.gewicht + json.aussehen = [ + basis.rasse.aussehen.aussehentext0, + basis.rasse.aussehen.aussehentext1, + basis.rasse.aussehen.aussehentext2, + basis.rasse.aussehen.aussehentext3, + ] + json.familie = [ + basis.rasse.aussehen.familietext0, + basis.rasse.aussehen.familietext1, + basis.rasse.aussehen.familietext2, + basis.rasse.aussehen.familietext3, + basis.rasse.aussehen.familietext4, + basis.rasse.aussehen.familietext5, + ] + json.titel = basis.rasse.aussehen.titel + json.stand = basis.rasse.aussehen.stand + json.attribute = {} + json.attribute.mu = getAttributeJson(eigenschaften, "Mut") + json.attribute.kl = getAttributeJson(eigenschaften, "Klugheit") + json.attribute.in = getAttributeJson(eigenschaften, "Intuition") + json.attribute.ch = getAttributeJson(eigenschaften, "Charisma") + json.attribute.ff = getAttributeJson(eigenschaften, "Fingerfertigkeit") + json.attribute.ge = getAttributeJson(eigenschaften, "Gewandtheit") + json.attribute.ko = getAttributeJson(eigenschaften, "Konstitution") + json.attribute.kk = getAttributeJson(eigenschaften, "Körperkraft") + json.attribute.mr = { + mod: filterAttribute(eigenschaften ,"Magieresistenz").mod + } + json.attribute.lep = { + mod: filterAttribute(eigenschaften ,"Lebensenergie").mod + } + json.attribute.aup = { + mod: filterAttribute(eigenschaften ,"Ausdauer").mod + } + json.attribute.asp = { + mod: filterAttribute(eigenschaften ,"Astralenergie").mod + } + json.attribute.kap = { + mod: filterAttribute(eigenschaften ,"Karmaenergie").mod + } + let attribute = filterAttribute(eigenschaften ,"Karmaenergie") + json.attribute.at = { + mod: attribute.mod, + aktuell: attribute.value + } + attribute = filterAttribute(eigenschaften ,"at") + json.attribute.pa = { + mod: attribute.mod, + aktuell: attribute.value + } + attribute = filterAttribute(eigenschaften ,"pa") + json.attribute.at = { + mod: attribute.mod, + aktuell: attribute.value + } + attribute = filterAttribute(eigenschaften ,"fk") + json.attribute.fk = { + mod: attribute.mod, + aktuell: attribute.value + } + attribute = filterAttribute(eigenschaften ,"ini") + json.attribute.ini = { + mod: attribute.mod, + aktuell: attribute.value + } + json.attribute.so = getAttributeJson(eigenschaften, "Sozialstatus") + let specialAbilities = [] + let liturgies = [] + for (specialAbility in sf) { + 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 (field in fields) { + if (field !== "name") { + let choices = specialAbility[field] + if (choices.hasOwnProperty("name")) { + specialAbilityJson.auswahlen.push(choices.name) + } else { + for (choice in choices) { + specialAbilityJson.auswahlen.push(choice.value) + } + } + } + } + } + specialAbilities.push(specialAbilityJson) + } + } + json.sonderfertigkeiten = specialAbilities + json.liturgien = liturgies + let talents = [] + for (talent in talentliste) { + talents.push({ + name: talent.name, + taw: talent.value, + probe: talent.probe.trim(), + be: talent.be, + komplexitaet: talent.k, + }) + } + json.talente = talents + let spells = [] + for (spell in zauberliste) { + spells.push({ + name: spell.name, + rep: spell.repraesentation, + hauszauber: spell.hauszauber === "true", + zfw: spell.value, + anmerkungen: spell.zauberkommentar, + komplexitaet: spell.k, + }) + } + json.zauber = spells + let combatValues = [] + for (combatValue in kampfwerte) { + combatValues.push({ + name: combatValue.name, + at: combatValue.attacke.value, + pa: combatValue.parade.value, + }) + } + json.kampfwerte = combatValues + let notes = [] + for (note in kommentare) { + if (note.hasOwnProperty("key")) { + notes.push({ + key: note.key, + notiz: note.kommentar, + }) + } + } + json.notizen = notes + } + + return json +} + +/** + * + * @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) +} \ No newline at end of file