const {HandlebarsApplicationMixin} = foundry.applications.api const {ActorSheetV2} = foundry.applications.sheets const {ContextMenu} = foundry.applications.ux export class GroupSheet extends HandlebarsApplicationMixin(ActorSheetV2) { /** @inheritDoc */ static DEFAULT_OPTIONS = { position: {width: 520, height: 480}, classes: ['dsa41', 'sheet', 'actor', 'group'], tag: 'form', dragDrop: [{ dropSelector: '.tab.inventory.active' }], form: { submitOnChange: true, closeOnSubmit: false, handler: GroupSheet.#onSubmitForm }, window: { resizable: true, }, actions: { roll: GroupSheet.#dieRoll, editImage: ActorSheetV2.DEFAULT_OPTIONS.actions.editImage, openEmbeddedDocument: GroupSheet.#openEmbeddedDocument, openActorDocument: GroupSheet.#openActorDocument, } } static TABS = { sheet: { tabs: [ {id: 'members', group: 'sheet', label: 'Gruppenmitglieder'}, {id: 'inventory', group: 'sheet', label: 'Gruppeninventar'}, ], initial: 'members' } } /** @inheritDoc */ static PARTS = { form: { template: `systems/DSA_4-1/templates/actor/group/main-sheet.hbs` }, members: { template: `systems/DSA_4-1/templates/actor/group/tab-members.hbs` }, inventory: { template: `systems/DSA_4-1/templates/actor/group/tab-inventory.hbs` }, settings: { template: `systems/DSA_4-1/templates/actor/group/tab-settings.hbs` } } constructor(options = {}) { super(options); } /** * Handle form submission * @this {AdvantageSheet} * @param {SubmitEvent} event * @param {HTMLFormElement} form * @param {FormDataExtended} formData */ static async #onSubmitForm(event, form, formData) { event.preventDefault() if (formData.object.name) { await (game.folders.get(this.document.system.groupId)).update({name: formData.object.name}) } await this.document.update(formData.object) // Note: formData.object } #stringToKeyFieldName(s) { return s } static #openEmbeddedDocument(event) { const dataset = event.target.parentElement.dataset const id = dataset.itemId ?? dataset.id this.document.items.get(id).sheet.render(true) } static async #openActorDocument(event) { const dataset = event.target.parentElement.dataset const id = dataset.itemId ?? dataset.id game.actors.get(id).sheet.render(true) } static async #dieRoll(evt) { } async #onUpdateCharacterSettings(data) { if (data.type === "character") { // update group let settings = {...this.document.system.settings} data.items.filter((i) => i.type === "Advantage").forEach((advantage) => { if (!settings[this.#stringToKeyFieldName(advantage.name)]) { settings[this.#stringToKeyFieldName(advantage.name)] = false } }) data.items.filter((i) => i.type === "Skill").forEach((skill) => { if (!settings[this.#stringToKeyFieldName(skill.name)]) { settings[this.#stringToKeyFieldName(skill.name)] = false } }) await this.document.update({"system.settings": settings}) } } _getTabsConfig(group) { const tabs = foundry.utils.deepClone(super._getTabsConfig(group)) // Modify tabs based on document properties if (game.user.isGM) { tabs.tabs.push({id: "settings", group: "sheet", label: "Einstellungen"}) } return tabs } /** @override */ async _prepareContext(options) { const context = await super._prepareContext(options) const groupData = context.document context.system = groupData.system context.flags = groupData.flags context.characters = [] context.isGM = game.user.isGM context.name = groupData.name context.img = groupData.img context.fields = []; const hiddenFields = Object.entries(groupData.system.settings) .sort(([key, value], [otherKey, otherValue]) => key.localeCompare(otherKey)) .filter(([key, value]) => value === true) .map(([key, value]) => key) context.fields = {} context.fields["head"] = {} for (const field of hiddenFields) { context.fields[field] = {} for (const characterId of groupData.system.characters) { const character = await game.actors.get(characterId) context.fields[field][character.name] = "-" } } // TODO hook on changes in the given folder const characters = await game.folders.get(groupData.system.groupId).contents for (const character of characters) { character.items.filter((i) => i.type === "Advantage").filter((i) => hiddenFields.includes(this.#stringToKeyFieldName(i.name))).map((advantage) => { const n = this.#stringToKeyFieldName(advantage.name) if (!context.fields[n]) { context.fields[n] = {} } context.fields[n][character.name] = advantage.system.value ?? "Ja" // TODO: Allow GM roll }) character.items.filter((i) => i.type === "Skill").filter((i) => hiddenFields.includes(this.#stringToKeyFieldName(i.name))).map((skill) => { const n = this.#stringToKeyFieldName(skill.name) if (!context.fields[n]) { context.fields[n] = {} } context.fields[n][character.name] = { taw: skill.system.taw ?? "0", name: skill.name, actor: character._id, } ?? 0 }) context.fields.head[character.name] = { img: character.img, name: character.name, id: character._id, attributes: [ {name: "MU", value: character.system.attribute.mu.aktuell}, {name: "KL", value: character.system.attribute.kl.aktuell}, {name: "IN", value: character.system.attribute.in.aktuell}, {name: "CH", value: character.system.attribute.ch.aktuell}, {name: "FF", value: character.system.attribute.ff.aktuell}, {name: "GE", value: character.system.attribute.ge.aktuell}, {name: "KO", value: character.system.attribute.ko.aktuell}, {name: "KK", value: character.system.attribute.kk.aktuell}, ], isLimited: character.isOwner || !character.limited, isVisible: character.isOwner || character.visible, isOwner: character.isOwner } } context.inventoryItems = []; const actorData = context.document; actorData.items.forEach((item, index) => { if (item.type === "Equipment") { context.inventoryItems.push({ index: index, id: item._id, quantity: item.system.quantity, name: item.name, icon: item.img }) } }) context.settings = Object.fromEntries(Object.entries(groupData.system.settings) .sort(([key, value], [otherKey, otherValue]) => key.localeCompare(otherKey))) return await context; } _onRender(context, options) { /*ContextMenu.implementation.create(this, this.element, ".equipment", [ { name: "Aus dem Gruppeninventar entfernen", icon: '', callback: (event) => { this.object.deleteEmbeddedDocuments('Item', [event[0].dataset.id]) }, condition: () => true } ], { jQuery: false });*/ // Drag-drop new foundry.applications.ux.DragDrop.implementation({ dragSelector: ".inventory-table .equipment", dropSelector: ".inventory-table", permissions: { dragstart: this._canDragStart.bind(this), drop: this._canDragDrop.bind(this) }, callbacks: { dragstart: this._onDragStart.bind(this), drop: this._onDrop.bind(this) } }).bind(this.element); // Update Group Members when either an Actor was moved into the linked Folder or removed from the linked Folder Hooks.on('updateActor', (data) => { if (data._id !== this.document._id) { // dont update yourself when you update yourself... baka! if (data.type === "character" && data.folder?._id === this.document.system.groupId) { this.#onUpdateCharacterSettings(data) this.render() } else if (data.type === "character") { this.render() } } }); } /** * An event that occurs when a drag workflow begins for a draggable item on the sheet. * @param {DragEvent} event The initiating drag start event * @returns {Promise} * @protected */ async _onDragStart(event) { const target = event.currentTarget; if ("link" in event.target.dataset) return; let dragData; // Owned Items if (target.dataset.itemId) { const item = this.actor.items.get(target.dataset.itemId); dragData = item.toDragData(); } // Active Effect if (target.dataset.effectId) { const effect = this.actor.effects.get(target.dataset.effectId); dragData = effect.toDragData(); } // Set data transfer if (!dragData) return; event.dataTransfer.setData("text/plain", JSON.stringify(dragData)); } // TODO needs to be fixed once Character Sheet is migrated to ActorSheetV2 async _onDrop(event) { const data = TextEditor.implementation.getDragEventData(event); const actor = this.actor; const allowed = Hooks.call("dropActorSheetData", actor, this, data); if (allowed === false) return; // Dropped Documents const documentClass = foundry.utils.getDocumentClass(data.type); if (documentClass) { const document = await documentClass.fromDropData(data); await this._onDropDocument(event, document); // No duplication by moving items from one actor to another if (document.parent) { document.parent.items.get(document._id).delete() } } } }