Talents can now be added or replaced via drag and drop

pull/43/head
macniel 2025-09-29 00:19:41 +02:00
parent 35271b8c83
commit 073c25e89a
11 changed files with 397 additions and 53 deletions

View File

@ -5,15 +5,16 @@ 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() {
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, {
@ -52,3 +55,7 @@ Hooks.once("init", () => {
return preloadHandlebarsTemplates();
})
Hooks.on('dropActorSheetData', (actor, sheet, data) => {
CharacterSheet.onDroppedData(actor, sheet, data);
} )

View File

@ -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);

View File

@ -24,4 +24,9 @@ export class Character extends Actor {
}
static onDroppedData(character, characterSheet, uuid) {
}
}

View File

@ -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<DropEffectValue>}
* @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";
}
};
}

View File

@ -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;
}
}

View File

@ -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,11 +88,14 @@ export class CharacterSheet extends ActorSheet {
];
context.skills = {};
context.flatSkills = [];
if ( context.system.talente?.length >= 0) {
context.system.talente.forEach(talent => {
context.system.talente.forEach( (talent, index) => {
if (talent.talent) {
const taw = talent.taw;
console.log(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 = [
@ -102,20 +108,25 @@ export class CharacterSheet extends ActorSheet {
context.skills[talentGruppe] = [];
}
context.skills[talentGruppe].push({
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);
}
})
}
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:
<input id="taw" type="number" value="${previousTaw}" />
`;
new Dialog({
title: `Talent ${item.name} ${alreadyInSet?'ersetzen':'hinzufügen'}`,
content: myContent,
buttons: {
button1: {
label: "hinzufügen",
callback: (html) => myCallback(html),
icon: `<i class="fas fa-check"></i>`
}
}
}).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()
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -4,6 +4,12 @@
<header class="sheet-header">
{{!-- Header stuff goes here --}}
<div class="header-fields">
<div class="editlock">
<label>
<input type="checkbox" name="isEditable" {{checked isEditable}}/>
<span>Editieren</span>
</label>
</div>
<div class="attribute">
{{#each attributes}}
{{> "systems/DSA_4-1/templates/ui/partial-attribute-button.hbs" this}}
@ -40,6 +46,14 @@
</div>
</div>
<div class="tab skills" data-group="primary" data-tab="skills">
{{#if this.isEditable}}
<h2>Talentwerte</h2>
<ul>
{{#each flatSkills}}
<li>{{> "systems/DSA_4-1/templates/ui/partial-talent-editable.hbs" this}}</li>
{{/each}}
</ul>
{{else}}
<h2>Körperliche Talente</h2>
<ul>
<li>
@ -99,6 +113,7 @@
</li>
{{/each}}
</ul>
{{/if}}
</div>
</section>
</form>

View File

@ -18,8 +18,8 @@
<div class="tab meta" data-group="primary" data-tab="meta">
<div>
<label>Kategorie
<select type="text" name="system.gruppe.value">
{{selectOptions categoryOptions selected=value}}
<select name="system.gruppe">
{{selectOptions categoryOptions selected=system.gruppe inverted=true}}
</select>
</label>
</div>
@ -58,9 +58,15 @@
</label>
</div>
<div>
<label>Voraussetzung
<input type="text" name="system.vorraussetzung" value="{{system.voraussetzung}}" />
</label>
<label>Voraussetzung</label>
<ul>
{{#each system.voraussetzung as |pair key|}}
<li>
<input type="text" name="system.vorraussetzung.{{key}}.wert" value="{{pair.wert}}" />
<input type="text" name="system.vorraussetzung.{{key}}.talent" value="{{pair.talent}}" />
</li>
{{/each}}
</ul>
</div>
</div>

View File

@ -0,0 +1,56 @@
<div class="block rollable {{this.type}}">
<div class="die">
<svg viewBox="0 0 6.3499998 6.35">
<g
id="g54292"
transform="matrix(0.18024007,0,0,0.18024007,-0.89816307,-0.85756211)"
style="stroke-width:1.05833;stroke-dasharray:none">
<path
style="stroke:#000000;stroke-width:1.05833;stroke-linejoin:bevel;stroke-dasharray:none"
d="M 22.60018,5.4074448 15.210951,18.121927 7.821723,13.87057 Z"
id="path54272"
class="die topleft" />
<path
style="stroke:#000000;stroke-width:1.05833;stroke-linejoin:bevel;stroke-dasharray:none"
d="M 22.60018,5.4074448 29.989407,18.121927 37.34771,13.890365 Z"
id="path54274"
class="die topright" />
<path
style="stroke:#000000;stroke-width:1.05833;stroke-linejoin:bevel;stroke-dasharray:none"
d="m 29.989407,18.121927 7.389229,12.754072 -2e-6,-17.005429 z"
id="path54276"
class="die bottomright" />
<path
style="display:inline;stroke:#000000;stroke-width:1.05833;stroke-linejoin:bevel;stroke-dasharray:none"
d="m 15.210951,18.121927 -7.3934235,12.756367 2e-6,-17.005429 z"
id="path54278"
class="die bottomleft" />
<path
style="display:inline;stroke:#000000;stroke-width:1.05833;stroke-linejoin:bevel;stroke-dasharray:none"
d="M 7.8175275,30.878293 22.600179,39.378712 37.378634,30.875999 Z"
id="path54280"
class="die bottom" />
<path
style="display:inline;stroke:#000000;stroke-width:1.05833;stroke-linejoin:bevel;stroke-dasharray:none"
d="M 7.821723,30.875999 22.600179,5.3678558 37.378634,30.875999 Z"
id="path54282"
class="die center" />
<path
style="fill-opacity:1;stroke:#000000;stroke-width:0.896643;stroke-dasharray:none;paint-order:normal"
id="path2181"
class="die border"
d="m 35.923409,110.09622 -12.498871,7.21622 -12.498871,-7.21622 V 95.663763 l 12.498871,-7.216227 12.498871,7.216227 z"
transform="matrix(1.1823833,0,0,1.1782771,-5.0966027,-98.847851)" />
</g>
</svg>
<span class="value">
</span>
</div>
<div class="container">
<span class="name">{{this.name}}</span>
<label class="taw"><span>TAW: </span>
<input type="text" name="{{this.tawPath}}.value" value="{{this.taw}}" />
</label>
</div>
</div>