export class CharacterSheet extends ActorSheet { /**@override */ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ['dsa41', 'sheet', 'actor', 'character'], width: 1100, height: 480, tabs: [ { navSelector: '.sheet-tabs', contentSelector: '.sheet-body', initial: 'description', }, ], }); } /** @override */ get template() { return `systems/DSA_4-1/templates/actor/actor-character-sheet.hbs`; } /** @override */ getData() { const context = super.getData(); // Use a safe clone of the actor data for further operations. const actorData = context.data; // Add the actor's data to context.data for easier access, as well as flags. context.system = actorData.system; context.flags = actorData.flags; this.#addSkillsToContext(context) this.#addAdvantagesToContext(context) this.#addAttributesToContext(context) this.#addEquipmentsToContext(context) return context; } #addSkillsToContext(context) { const actorData = context.data; context.skills = {}; context.flatSkills = []; Object.values(actorData.items).forEach( (item, index) => { if (item.type === "Skill") { const talentGruppe = item.system.gruppe; const eigenschaften = Object.values(item.system.probe); const werte = [ {name: eigenschaften[0], value: this.prepareEigenschaftRoll(actorData, eigenschaften[0])}, {name: eigenschaften[1], value: this.prepareEigenschaftRoll(actorData, eigenschaften[1])}, {name: eigenschaften[2], value: this.prepareEigenschaftRoll(actorData, eigenschaften[2])} ] if (context.skills[talentGruppe] == null) { context.skills[talentGruppe] = []; } const obj = { type: "talent", gruppe: talentGruppe, name: item.name, taw: "" + item.system.taw, tawPath: `system.items.${index}.taw`, werte, rollEigenschaft1: werte[0].value, rollEigenschaft2: werte[1].value, rollEigenschaft3: werte[2].value, eigenschaft1: werte[0].name, eigenschaft2: werte[1].name, eigenschaft3: werte[2].name, probe: `(${eigenschaften.join("/")})`, id: item._id, }; context.skills[talentGruppe].push(obj); context.flatSkills.push(obj); } } ); } #addAdvantagesToContext(context) { context.advantages = []; const actorData = context.data; Object.values(actorData.items).forEach( (item) => { if (item.type === "Advantage") { context.advantages.push({ id: item._id, name: item.name, value: item.system.value, options: item.system.auswahl, description: item.system.description, }); } } ); } #addAttributesToContext(context) { const actorData = context.data; context.attributes = [ { eigenschaft: "mu", name: "MU", tooltip: "Mut", wert: actorData.system.attribute.mu.aktuell ?? 0, }, { eigenschaft: "kl", name: "KL", tooltip: "Klugheit", wert: actorData.system.attribute.kl.aktuell ?? 0, }, { eigenschaft: "in", name: "IN", tooltip: "Intuition", wert: actorData.system.attribute.in.aktuell ?? 0, }, { eigenschaft: "ch", name: "CH", tooltip: "Charisma", wert: actorData.system.attribute.ch.aktuell ?? 0, }, { eigenschaft: "ff", name: "FF", tooltip: "Fingerfertigkeit", wert: actorData.system.attribute.ff.aktuell ?? 0, }, { eigenschaft: "ge", name: "GE", tooltip: "Geschicklichkeit", wert: actorData.system.attribute.ge.aktuell ?? 0, }, { eigenschaft: "ko", name: "KO", tooltip: "Konstitution", wert: actorData.system.attribute.ko.aktuell ?? 0, }, { eigenschaft: "kk", name: "KK", tooltip: "Körperkraft", wert: actorData.system.attribute.kk.aktuell ?? 0, }, ]; } #addEquipmentsToContext(context) { context.equipments = []; const actorData = context.data; context.carryingweight = 0; Object.values(actorData.items).forEach( (item, index) => { if (item.type === "Equipment") { context.equipments.push({ index: index, id: item._id, quantity: item.system.quantity, name: item.name, }) context.carryingweight += item.system.quantity * item.system.weight; } }) context.maxcarryingcapacity = actorData.system.attribute.kk.aktuell context.carryingpercentage = (context.carryingweight / context.maxcarryingcapacity)*100; } prepareEigenschaftRoll(actorData, name) { return actorData.system.attribute[name.toLowerCase()].aktuell } async _onTalentRoll(event) { event.preventDefault(); const dataset = event.currentTarget.dataset; console.log(dataset) if (dataset.rolleigenschaft1) { let roll1 = new Roll("3d20", this.actor.getRollData()); let evaluated1 = (await roll1.evaluate()) const dsaDieRollEvaluated = this._evaluateRoll(evaluated1.terms[0].results, { taw: dataset.taw, werte: [dataset.rolleigenschaft1, dataset.rolleigenschaft2, dataset.rolleigenschaft3], }) if (dsaDieRollEvaluated.tap >= 0) { // erfolg evaluated1.toMessage({ speaker: ChatMessage.getSpeaker({actor: this.actor}), flavor: ` ${dsaDieRollEvaluated.meisterlich?'Meisterlich geschafft':'Geschafft'} mit ${dsaDieRollEvaluated.tap} Punkten übrig`, rollMode: game.settings.get('core', 'rollMode'), }) } else { // misserfolg evaluated1.toMessage({ speaker: ChatMessage.getSpeaker({actor: this.actor}), flavor: ` ${dsaDieRollEvaluated.meisterlich?'Gepatzt':''} mit ${Math.abs(dsaDieRollEvaluated.tap)} Punkten daneben`, rollMode: game.settings.get('core', 'rollMode'), }) } } } _evaluateRoll(rolledDice, { taw, lowerThreshold = 1, upperThreshold = 20, countToMeisterlich = 3, countToPatzer = 3, werte = [] } ) { let tap = taw; let meisterlichCounter = 0; let patzerCounter = 0; let failCounter = 0; rolledDice.forEach( (rolledDie, index) => { if (tap < 0 && rolledDie.result > werte[index]) { tap -= rolledDie.result - werte[index]; if (tap <0) { // konnte nicht vollständig ausgeglichen werden failCounter++; } } else if (rolledDie.result > werte[index]) { // taw ist bereits aufgebraucht und wert kann nicht ausgeglichen werden tap -= rolledDie.result - werte[index]; failCounter++; } if (rolledDie.result <= lowerThreshold) meisterlichCounter++; if (rolledDie.result > upperThreshold) patzerCounter++; }) return { tap, meisterlich: meisterlichCounter === countToMeisterlich, patzer: patzerCounter === countToPatzer, } } _onAttributeRoll(event) { event.preventDefault(); const dataset = event.currentTarget.dataset; if (dataset.roll) { let label = dataset.label ? `[Attribut] ${dataset.label}` : ''; let roll = new Roll(dataset.roll, this.actor.getRollData()); roll.toMessage({ speaker: ChatMessage.getSpeaker({ actor: this.actor }), flavor: label, rollMode: game.settings.get('core', 'rollMode'), }); return roll; } } _onRoll(event) { event.preventDefault(); const dataset = event.currentTarget.dataset; if (dataset.roll) { let label = dataset.label ? `${dataset.label}` : ''; let roll = new Roll(dataset.roll, this.actor.getRollData()); roll.toMessage({ speaker: ChatMessage.getSpeaker({ actor: this.actor }), flavor: label, rollMode: game.settings.get('core', 'rollMode'), }); return roll; } } openEmbeddedDocument(documentId) { this.object.items.get(documentId).sheet.render(true) } showAdjustAttributeDialog(attributeName, attributeField, previousValue) { const thisActor = this; const myContent = ` Value: `; function updateAttribute(html) { const value = html.find("input#attributeValue").val(); const attribute = {} attribute[attributeField.toLowerCase()] = { aktuell: value } thisActor.object.update({ system: { attribute }}) } new Dialog({ title: `${attributeName} ändern auf`, content: myContent, buttons: { button1: { label: "Ändern", callback: (html) => { updateAttribute(html) }, icon: `` } } }).render(true); } activateListeners(html) { super.activateListeners(html); html.on('click', '.attribute.rollable', (evt) => { this._onAttributeRoll(evt); }); html.on('click', '.talent.rollable', (evt) => { this._onTalentRoll(evt); }); html.on('click', '.sidebar-element.rollable', (evt) => { this._onRoll(evt); }); html.on('click', '.talent .name', (evt) => { this.openEmbeddedDocument(evt.target.dataset.id); evt.stopPropagation(); }) html.on('click', '.advantage .name', (evt) => { this.openEmbeddedDocument(evt.target.dataset.id); evt.stopPropagation(); }) html.on('click', '.equipment', (evt) => { this.openEmbeddedDocument(evt.target.parentElement.dataset.id); evt.stopPropagation(); }) new ContextMenu(html, '.talent.rollable', [ { name: "Entfernen", icon: '', callback: (event) => { this.object.deleteEmbeddedDocuments('Item', [event[0].dataset.id]) }, condition: () => true } ]); new ContextMenu(html, '.attribute.rollable', [ { name: "Anpassen", icon: '', callback: (event) => { this.showAdjustAttributeDialog(event[0].dataset.name, event[0].dataset.label, event[0].dataset.value) }, condition: () => true } ]); let handler = ev => this._onDragStart(ev); // Find all items on the character sheet. html.find('.talent.rollable').each((i, li) => { // Add draggable attribute and dragstart listener. li.setAttribute("draggable", true); li.addEventListener("dragstart", handler, false); }); new ContextMenu(html, '.equipment', [ { name: "Aus dem Inventar entfernen", icon: '', callback: (event) => { this.object.deleteEmbeddedDocuments('Item', [event[0].dataset.id]) }, condition: () => true } ]); } #handleDroppedSkill(actor, skill) { const array = Array.from(actor.items); for ( let i = 0; i < array.length; i++ ) { if (array[i].name === skill.name) { return false; } } } #handleDroppedAdvantage(actor, advantage) { const array = Array.from(actor.items); for ( let i = 0; i < array.length; i++ ) { if (array[i].name === advantage.name) { // TODO: adjust for uniqueness return false; } } } #handleDroppedEquipment(actor, equipment) { const array = Array.from(actor.items); for ( let i = 0; i < array.length; i++ ) { if (array[i].name === equipment.name) { // TODO: adjust item quantity if item is the same console.log(equipment); return false; } } } static getElementByName(collection, id) { const array = Array.from(collection); for (const element of array) { if (element._id === id) { return element; } } } static onDroppedData(actor, characterSheet, data) { const uuid = foundry.utils.parseUuid(data.uuid); const collection = uuid.collection.index ?? uuid.collection; const document = CharacterSheet.getElementByName(collection, uuid.id); const { name, type } = document console.log(name, type) switch (type) { case "Skill": return characterSheet.#handleDroppedSkill(actor, document); // on false cancel this whole operation case "Advantage": return characterSheet.#handleDroppedAdvantage(actor, document); case "Equipment": return characterSheet.#handleDroppedEquipment(actor, document); default: return false; } } }