From 073c25e89acd49ea43d7886403661efe0fcc6813 Mon Sep 17 00:00:00 2001 From: macniel Date: Mon, 29 Sep 2025 00:19:41 +0200 Subject: [PATCH] Talents can now be added or replaced via drag and drop --- src/main.mjs | 31 +++-- src/module/data/character.mjs | 6 +- src/module/documents/character.mjs | 5 + .../extensions/DragDropApplicationMixin.mjs | 62 +++++++++ src/module/extensions/DragDropDSA41.mjs | 54 ++++++++ src/module/sheets/characterSheet.mjs | 128 ++++++++++++++---- src/module/sheets/skillSheet.mjs | 74 ++++++++-- src/style/_rollable.scss | 3 +- src/templates/actor/actor-character-sheet.hbs | 15 ++ src/templates/item/item-Skill-sheet.hbs | 16 ++- src/templates/ui/partial-talent-editable.hbs | 56 ++++++++ 11 files changed, 397 insertions(+), 53 deletions(-) create mode 100644 src/module/extensions/DragDropApplicationMixin.mjs create mode 100644 src/module/extensions/DragDropDSA41.mjs create mode 100644 src/templates/ui/partial-talent-editable.hbs diff --git a/src/main.mjs b/src/main.mjs index a5399ff0..00a06367 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -1,19 +1,20 @@ -import { PlayerCharacterDataModel } from "./module/data/character.mjs"; -import { SkillSheet } from "./module/sheets/skillSheet.mjs"; -import { SpellSheet } from "./module/sheets/spellSheet.mjs"; -import { CharacterSheet } from "./module/sheets/characterSheet.mjs"; -import { SkillDataModel } from "./module/data/skill.mjs"; -import { SpellDataModel } from "./module/data/spell.mjs"; -import { Character } from "./module/documents/character.mjs"; +import {PlayerCharacterDataModel} from "./module/data/character.mjs"; +import {SkillSheet} from "./module/sheets/skillSheet.mjs"; +import {SpellSheet} from "./module/sheets/spellSheet.mjs"; +import {CharacterSheet} from "./module/sheets/characterSheet.mjs"; +import {SkillDataModel} from "./module/data/skill.mjs"; +import {SpellDataModel} from "./module/data/spell.mjs"; +import {Character} from "./module/documents/character.mjs"; +import {DragDropDSA41} from "./module/extensions/DragDropDSA41.mjs"; -async function preloadHandlebarsTemplates () { +async function preloadHandlebarsTemplates() { return loadTemplates([ // ui partials. 'systems/DSA_4-1/templates/ui/partial-rollable-button.hbs', - 'systems/DSA_4-1/templates/ui/partial-attribute-button.hbs' + 'systems/DSA_4-1/templates/ui/partial-attribute-button.hbs', + 'systems/DSA_4-1/templates/ui/partial-talent-editable.hbs' ]); -}; - +} Hooks.once("init", () => { @@ -30,6 +31,8 @@ Hooks.once("init", () => { spell: SpellDataModel } + CONFIG.ux.DragDrop = DragDropDSA41; + console.log("DSA 4.1 is ready for development!") Actors.registerSheet('dsa41.character', CharacterSheet, { @@ -51,4 +54,8 @@ Hooks.once("init", () => { }); return preloadHandlebarsTemplates(); -}) \ No newline at end of file +}) + +Hooks.on('dropActorSheetData', (actor, sheet, data) => { + CharacterSheet.onDroppedData(actor, sheet, data); +} ) \ No newline at end of file diff --git a/src/module/data/character.mjs b/src/module/data/character.mjs index bd77fa8e..e30a1d89 100644 --- a/src/module/data/character.mjs +++ b/src/module/data/character.mjs @@ -53,7 +53,7 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel { super._initialize(options); } - _onCreate(data, options, userId) { + async _onCreate(data, options, userId) { // prepare base talents const talentsByName = [ "Athletik", "Klettern", "Körperbeherrschung", "Schleichen", "Schwimmen", "Selbstbeherrschung", "Sich Verstecken", "Singen", "Sinnenschärfe", "Tanzen", "Zechen", @@ -79,7 +79,7 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel { }) // push base talents - game.actors.getName(data.name).update({system: {talente}}) + await game.actors.getName(data.name).update({system: {talente}}) const startEigenschaften = { "mu": 10, @@ -92,7 +92,7 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel { "kk": 10, } - game.actors.getName(data.name).update({system: {attribute: startEigenschaften}}) + await game.actors.getName(data.name).update({system: {attribute: startEigenschaften}}) super._onCreate(data, options, userId); diff --git a/src/module/documents/character.mjs b/src/module/documents/character.mjs index a17df7be..9b5f5487 100644 --- a/src/module/documents/character.mjs +++ b/src/module/documents/character.mjs @@ -24,4 +24,9 @@ export class Character extends Actor { } + static onDroppedData(character, characterSheet, uuid) { + + } + + } \ No newline at end of file diff --git a/src/module/extensions/DragDropApplicationMixin.mjs b/src/module/extensions/DragDropApplicationMixin.mjs new file mode 100644 index 00000000..ecc9284a --- /dev/null +++ b/src/module/extensions/DragDropApplicationMixin.mjs @@ -0,0 +1,62 @@ +import {DragDropDSA41} from "./DragDropDSA41.mjs"; + +export default function DragDropApplicationMixin(Base) { + return class DragDropApplication extends Base { + /** @override */ + _onDragOver(event) { + const data = DragDropDSA41.getPayload(event); + DragDropDSA41.dropEffect = event.dataTransfer.dropEffect = (foundry.utils.getType(data) === "Object") + ? this._dropBehavior(event, data) : "copy"; + } + + /* -------------------------------------------- */ + + /** + * The behavior for the dropped data. When called during the drop event, ensure this is called before awaiting + * anything or the drop behavior will be lost. + * @param {DragEvent} event The drag event. + * @param {object} [data] The drag payload. + * @returns {DropEffectValue} + */ + _dropBehavior(event, data) { + data ??= DragDropDSA41.getPayload(event); + const allowed = this._allowedDropBehaviors(event, data); + let behavior = DragDropDSA41.dropEffect ?? event.dataTransfer?.dropEffect; + + if ( event.type === "dragover" ) { + if ( areKeysPressed(event, "dragMove") ) behavior = "move"; + else if ( areKeysPressed(event, "dragCopy") ) behavior = "copy"; + else behavior = this._defaultDropBehavior(event, data); + } + + if ( (behavior !== "none") && !allowed.has(behavior) ) return allowed.first() ?? "none"; + return behavior || "copy"; + } + + /* -------------------------------------------- */ + + /** + * Types of allowed drop behaviors based on the origin & target of a drag event. + * @param {DragEvent} event The drag event. + * @param {object} [data] The drag payload. + * @returns {Set} + * @protected + */ + _allowedDropBehaviors(event, data) { + return new Set(); + } + + /* -------------------------------------------- */ + + /** + * Determine the default drop behavior for the provided operation. + * @param {DragEvent} event The drag event. + * @param {object} [data] The drag payload. + * @returns {DropEffectValue} + * @protected + */ + _defaultDropBehavior(event, data) { + return "copy"; + } + }; +} diff --git a/src/module/extensions/DragDropDSA41.mjs b/src/module/extensions/DragDropDSA41.mjs new file mode 100644 index 00000000..6344455e --- /dev/null +++ b/src/module/extensions/DragDropDSA41.mjs @@ -0,0 +1,54 @@ +export class DragDropDSA41 extends foundry.applications.ux.DragDrop { + + /** + * Drop effect used for current drag operation. + * @type {DropEffectValue|null} + */ + static dropEffect = null; + + /* -------------------------------------------- */ + + /** + * Stored drag event payload. + * @type {{ data: any, event: DragEvent }|null} + */ + static #payload = null; + + /* -------------------------------------------- */ + + /** @override */ + async _handleDragStart(event) { + await this.callback(event, "dragstart"); + if ( event.dataTransfer.items.length ) { + console.log(event) + event.stopPropagation(); + let data = event.dataTransfer.getData("application/json") || event.dataTransfer.getData("text/plain"); + try { data = JSON.parse(data); } catch(err) {} + DragDropDSA41.#payload = data ? { event, data } : null; + } else { + DragDropDSA41.#payload = null; + } + } + + /* -------------------------------------------- */ + + /** @override */ + async _handleDragEnd(event) { + await this.callback(event, "dragend"); + DragDropDSA41.dropEffect = null; + DragDropDSA41.#payload = null; + } + + /* -------------------------------------------- */ + + /** + * Get the data payload for the current drag event. + * @param {DragEvent} event + * @returns {any} + */ + static getPayload(event) { + if ( !DragDropDSA41.#payload?.data ) return null; + return DragDropDSA41.#payload.data; + } + +} \ No newline at end of file diff --git a/src/module/sheets/characterSheet.mjs b/src/module/sheets/characterSheet.mjs index e8ea7c66..8c38b014 100644 --- a/src/module/sheets/characterSheet.mjs +++ b/src/module/sheets/characterSheet.mjs @@ -1,3 +1,5 @@ +import {DragDropDSA41} from "../extensions/DragDropDSA41.mjs"; + export class CharacterSheet extends ActorSheet { /**@override */ static get defaultOptions() { @@ -34,6 +36,7 @@ export class CharacterSheet extends ActorSheet { // Add the actor's data to context.data for easier access, as well as flags. context.system = actorData.system; context.flags = actorData.flags; + context.isEditable = actorData.editable; context.attributes = [ { eigenschaft: "mu", @@ -85,37 +88,45 @@ export class CharacterSheet extends ActorSheet { ]; context.skills = {}; + context.flatSkills = []; + if ( context.system.talente?.length >= 0) { - context.system.talente.forEach(talent => { - const taw = talent.taw; - console.log(taw); - const talentObjekt = game.items.get(talent.talent); - const talentGruppe = talentObjekt.system.gruppe; - const eigenschaften = Object.values(talentObjekt.system.probe); - const werte = [ - {name: eigenschaften[0], value: this.prepareEigenschaftRoll(actorData, eigenschaften[0])}, + context.system.talente.forEach( (talent, index) => { + if (talent.talent) { + const taw = talent.taw; + const talentObjekt = game.items.get(talent.talent); + console.log(talent); + const talentGruppe = talentObjekt.system.gruppe; + const eigenschaften = Object.values(talentObjekt.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] = []; + if (context.skills[talentGruppe] == null) { + context.skills[talentGruppe] = []; + } + + const obj = { + type: "talent", + gruppe: talentGruppe, + name: talentObjekt.name, + taw: "" + taw, + tawPath: `system.talente.${index}.taw`, + werte, + rollEigenschaft1: werte[0].value, + rollEigenschaft2: werte[1].value, + rollEigenschaft3: werte[2].value, + probe: `(${eigenschaften.join("/")})` + }; + + context.skills[talentGruppe].push(obj); + context.flatSkills.push(obj); } - - context.skills[talentGruppe].push({ - type: "talent", - gruppe: talentGruppe, - name: talentObjekt.name, - taw: ""+taw, - werte, - rollEigenschaft1: werte[0].value, - rollEigenschaft2: werte[1].value, - rollEigenschaft3: werte[2].value, - probe: `(${eigenschaften.join("/")})` - }); - }) } + console.log(context); return context; } @@ -215,4 +226,73 @@ export class CharacterSheet extends ActorSheet { } + static onDroppedData(actor, characterSheet, data) { + const item = game.items.get(foundry.utils.parseUuid(data.uuid).id) + console.log(); + let alreadyInSet = false; + let previousTaw = 0; + actor.system.talente.forEach(({taw, talent}) => { + if (talent._id === item._id) { + alreadyInSet = talent; + previousTaw = taw; + } + }) + + + + const myContent = ` + TaW: + +`; + new Dialog({ + title: `Talent ${item.name} ${alreadyInSet?'ersetzen':'hinzufügen'}`, + content: myContent, + buttons: { + button1: { + label: "hinzufügen", + callback: (html) => myCallback(html), + icon: `` + } + } + }).render(true); + + async function myCallback(html) { + const taw = html.find("input#taw").val(); + + let index = actor.system.talente.findIndex( predicate => predicate.talent._id === alreadyInSet._id ) + let sorted = []; + if (alreadyInSet) { + actor.system.talente[index].taw = taw; + sorted = actor.system.talente; + + } else { + sorted = [{ + taw: taw, + talent: {_id: item._id, name: item.name} + }, ...actor.system.talente].sort((a, b) => a.talent.name.localeCompare(b.talent.name)); + } + + const serialised = sorted.map(({taw, talent}) => { + return { + taw: taw, + talent: talent._id + } + }); + + await actor.update({ + system: { + talente: [ + + ...serialised + ] + } + }); + await characterSheet.render(true); + ui.notifications.info(`Talent ${item.name} auf TaW ${taw} hinzugefügt`); + + } + + actor.items.clear() + } + } \ No newline at end of file diff --git a/src/module/sheets/skillSheet.mjs b/src/module/sheets/skillSheet.mjs index 59d17497..a139b05e 100644 --- a/src/module/sheets/skillSheet.mjs +++ b/src/module/sheets/skillSheet.mjs @@ -1,4 +1,7 @@ -export class SkillSheet extends foundry.appv1.sheets.ItemSheet { +import {DragDropDSA41} from "../extensions/DragDropDSA41.mjs"; +import DragDropApplicationMixin from "../extensions/DragDropApplicationMixin.mjs"; + +export class SkillSheet extends DragDropApplicationMixin(foundry.appv1.sheets.ItemSheet) { /**@override */ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { @@ -35,13 +38,14 @@ export class SkillSheet extends foundry.appv1.sheets.ItemSheet { context.system = skillData.system; context.flags = skillData.flags; context.categoryOptions = { - kampf: "Kampf", - körperlich: "Körperlich", - gesellschaft: "Gesellschaft", - natur: "Natur", - wissen: "Wissen", - sprachen: "Sprache und Schriften", - handwerk: "Handwerk" + Kampf: "Kampf", + Körperlich: "Körperlich", + Gesellschaft: "Gesellschaft", + Natur: "Natur", + Wissen: "Wissen", + Sprachen: "Sprache", + Schriften: "Schriften", + Handwerk: "Handwerk" } return context; @@ -52,6 +56,60 @@ export class SkillSheet extends foundry.appv1.sheets.ItemSheet { // Everything below here is only needed if the sheet is editable if (!this.isEditable) return; + } + + + /* -------------------------------------------- */ + /* Drag & Drop */ + /* -------------------------------------------- */ + + /** @override */ + _allowedDropBehaviors(event, data) { + console.log(data, event); + if ( !data?.uuid ) return new Set(["copy", "link"]); + const allowed = new Set(["copy", "move", "link"]); + const s = foundry.utils.parseUuid(data.uuid); + const t = foundry.utils.parseUuid(this.document.uuid); + const sCompendium = s.collection instanceof foundry.documents.collections.CompendiumCollection; + const tCompendium = t.collection instanceof foundry.documents.collections.CompendiumCollection; + + // If either source or target are within a compendium, but not inside the same compendium, move not allowed + if ( (sCompendium || tCompendium) && (s.collection !== t.collection) ) allowed.delete("move"); + + return allowed; + } + + /* -------------------------------------------- */ + + /** @override */ + _defaultDropBehavior(event, data) { + if ( !data?.uuid ) return "copy"; + const d = foundry.utils.parseUuid(data.uuid); + const t = foundry.utils.parseUuid(this.document.uuid); + const base = d.embedded?.length ? "document" : "primary"; + console.log(d, t, base); + return (d.collection === t.collection) && (d[`${base}Id`] === t[`${base}Id`]) + && (d[`${base}Type`] === t[`${base}Type`]) ? "move" : "copy"; + } + + /* -------------------------------------------- */ + + /** @inheritDoc */ + async _onDragStart(event) { + await super._onDragStart(event); + if ( !this.document.isOwner || this.document.collection?.locked ) { + event.dataTransfer.effectAllowed = "copyLink"; + } + } + + _onDragOver(event) { + super._onDragOver(event); + console.log(event); + } + + _dropBehavior(event, data) { + console.log(event, data); + return super._dropBehavior(event, data); } diff --git a/src/style/_rollable.scss b/src/style/_rollable.scss index 86083bf6..aa2d0cb0 100644 --- a/src/style/_rollable.scss +++ b/src/style/_rollable.scss @@ -72,13 +72,14 @@ $rollable_colours: ( position: absolute; right: 0; height: 32px; + top: 0; .eigenschaft { display: inline-block; height: 32px; width: 32px; position: relative; - top: -32px; + top: 0; span.name { position: absolute; diff --git a/src/templates/actor/actor-character-sheet.hbs b/src/templates/actor/actor-character-sheet.hbs index a43bee09..ff1d94f6 100644 --- a/src/templates/actor/actor-character-sheet.hbs +++ b/src/templates/actor/actor-character-sheet.hbs @@ -4,6 +4,12 @@
{{!-- Header stuff goes here --}}
+
+ +
{{#each attributes}} {{> "systems/DSA_4-1/templates/ui/partial-attribute-button.hbs" this}} @@ -40,6 +46,14 @@
+ {{#if this.isEditable}} +

Talentwerte

+
    + {{#each flatSkills}} +
  • {{> "systems/DSA_4-1/templates/ui/partial-talent-editable.hbs" this}}
  • + {{/each}} +
+ {{else}}

Körperliche Talente

  • @@ -99,6 +113,7 @@
  • {{/each}}
+ {{/if}}
\ No newline at end of file diff --git a/src/templates/item/item-Skill-sheet.hbs b/src/templates/item/item-Skill-sheet.hbs index efb2ee6a..b0c0488b 100644 --- a/src/templates/item/item-Skill-sheet.hbs +++ b/src/templates/item/item-Skill-sheet.hbs @@ -18,8 +18,8 @@
@@ -58,9 +58,15 @@
- + +
    + {{#each system.voraussetzung as |pair key|}} +
  • + + +
  • + {{/each}} +
diff --git a/src/templates/ui/partial-talent-editable.hbs b/src/templates/ui/partial-talent-editable.hbs new file mode 100644 index 00000000..6db394ab --- /dev/null +++ b/src/templates/ui/partial-talent-editable.hbs @@ -0,0 +1,56 @@ +
+ +
+ + + + + + + + + + + + + +
+
+ {{this.name}} + +
+
\ No newline at end of file