From fb0add4cbcd1b69213d0d35ec193524559cac266 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 18 May 2021 17:36:07 -0400 Subject: [PATCH] Update Simple Worldbuilding for Foundry 0.8.x Compatibility --- lang/en.json | 9 +- module/actor-sheet.js | 93 ++++++++------ module/actor.js | 82 +++++++------ module/helper.js | 176 +++++++++++++++------------ module/item-sheet.js | 44 +++---- module/item.js | 22 ++++ module/simple.js | 92 ++------------ system.json | 11 +- templates/actor-sheet.html | 33 ++--- templates/item-sheet.html | 19 +-- templates/sidebar/entity-create.html | 15 --- 11 files changed, 295 insertions(+), 301 deletions(-) create mode 100644 module/item.js delete mode 100644 templates/sidebar/entity-create.html diff --git a/lang/en.json b/lang/en.json index 05254bd..797608e 100644 --- a/lang/en.json +++ b/lang/en.json @@ -4,8 +4,13 @@ "SETTINGS.SimpleInitFormulaN": "Initiative Formula", "SETTINGS.SimpleInitFormulaL": "Enter an initiative formula, such as d20+@dex", - "SIMPLE.NotifyInitFormulaUpdated": "Initiative formula was updated to:", - "SIMPLE.NotifyInitFormulaInvalid": "Initiative formula was invalid:", + "SIMPLE.ItemCreate": "Create Item", + "SIMPLE.ItemEdit": "Edit Item", + "SIMPLE.ItemDelete": "Delete Item", + "SIMPLE.ItemNew": "New Item", + + "SIMPLE.NotifyInitFormulaUpdated": "Initiative formula was updated to", + "SIMPLE.NotifyInitFormulaInvalid": "Initiative formula was invalid", "SIMPLE.NotifyGroupDuplicate": "Attribute group already exists.", "SIMPLE.NotifyGroupAttrDuplicate": "Attribute group already exists as an attribute.", "SIMPLE.NotifyGroupAlphanumeric": "Attribute group names may not contain spaces or periods.", diff --git a/module/actor-sheet.js b/module/actor-sheet.js index 91696bd..5149792 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -1,4 +1,5 @@ import { EntitySheetHelper } from "./helper.js"; +import {ATTRIBUTE_TYPES} from "./constants.js"; /** * Extend the basic ActorSheet with some very simple modifications @@ -6,9 +7,9 @@ import { EntitySheetHelper } from "./helper.js"; */ export class SimpleActorSheet extends ActorSheet { - /** @override */ + /** @inheritdoc */ static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { classes: ["worldbuilding", "sheet", "actor"], template: "systems/worldbuilding/templates/actor-sheet.html", width: 600, @@ -21,42 +22,35 @@ export class SimpleActorSheet extends ActorSheet { /* -------------------------------------------- */ - /** @override */ + /** @inheritdoc */ getData() { - const data = super.getData(); - EntitySheetHelper.getAttributeData(data); - data.shorthand = !!game.settings.get("worldbuilding", "macroShorthand"); - return data; + const context = super.getData(); + EntitySheetHelper.getAttributeData(context.data); + context.shorthand = !!game.settings.get("worldbuilding", "macroShorthand"); + context.systemData = context.data.data; + context.dtypes = ATTRIBUTE_TYPES; + return context; } /* -------------------------------------------- */ - /** @override */ + /** @inheritdoc */ activateListeners(html) { super.activateListeners(html); // Everything below here is only needed if the sheet is editable - if ( !this.options.editable ) return; + if ( !this.isEditable ) return; - // Handle rollable items and attributes - html.find(".items .rollable").on("click", this._onItemRoll.bind(this)); + // Attribute Management + html.find(".attributes").on("click", ".attribute-control", EntitySheetHelper.onClickAttributeControl.bind(this)); + html.find(".groups").on("click", ".group-control", EntitySheetHelper.onClickAttributeGroupControl.bind(this)); html.find(".attributes").on("click", "a.attribute-roll", EntitySheetHelper.onAttributeRoll.bind(this)); - // Update Inventory Item - html.find('.item-edit').click(ev => { - const li = $(ev.currentTarget).parents(".item"); - const item = this.actor.getOwnedItem(li.data("itemId")); - item.sheet.render(true); - }); + // Item Controls + html.find(".item-control").click(this._onItemControl.bind(this)); + html.find(".items .rollable").on("click", this._onItemRoll.bind(this)); - // Delete Inventory Item - html.find('.item-delete').click(ev => { - const li = $(ev.currentTarget).parents(".item"); - this.actor.deleteOwnedItem(li.data("itemId")); - li.slideUp(200, () => this.render(false)); - }); - - // Add draggable for macros. + // Add draggable for Macro creation html.find(".attributes a.attribute-roll").each((i, a) => { a.setAttribute("draggable", true); a.addEventListener("dragstart", ev => { @@ -64,12 +58,33 @@ export class SimpleActorSheet extends ActorSheet { ev.dataTransfer.setData('text/plain', JSON.stringify(dragData)); }, false); }); + } - // Add or Remove Attribute - html.find(".attributes").on("click", ".attribute-control", EntitySheetHelper.onClickAttributeControl.bind(this)); + /* -------------------------------------------- */ - // Add attribute groups. - html.find(".groups").on("click", ".group-control", EntitySheetHelper.onClickAttributeGroupControl.bind(this)); + /** + * Handle click events for Item control buttons within the Actor Sheet + * @param event + * @private + */ + _onItemControl(event) { + event.preventDefault(); + + // Obtain event data + const button = event.currentTarget; + const li = button.closest(".item"); + const item = this.actor.items.get(li?.dataset.itemId); + + // Handle different actions + switch ( button.dataset.action ) { + case "create": + const cls = getDocumentClass("Item"); + return cls.create({name: game.i18n.localize("SIMPLE.ItemNew"), type: "item"}, {parent: this.actor}); + case "edit": + return item.sheet.render(true); + case "delete": + return item.delete(); + } } /* -------------------------------------------- */ @@ -80,11 +95,11 @@ export class SimpleActorSheet extends ActorSheet { */ _onItemRoll(event) { let button = $(event.currentTarget); - let r = new Roll(button.data('roll'), this.actor.getRollData()); const li = button.parents(".item"); - const item = this.actor.getOwnedItem(li.data("itemId")); - r.roll().toMessage({ - user: game.user._id, + const item = this.actor.items.get(li.data("itemId")); + let r = new Roll(button.data('roll'), this.actor.getRollData()); + return r.toMessage({ + user: game.user.id, speaker: ChatMessage.getSpeaker({ actor: this.actor }), flavor: `

${item.name}

${button.text()}

` }); @@ -92,11 +107,11 @@ export class SimpleActorSheet extends ActorSheet { /* -------------------------------------------- */ - /** @override */ - _updateObject(event, formData) { - formData = EntitySheetHelper.updateAttributes(formData, this); - formData = EntitySheetHelper.updateGroups(formData, this); - return this.object.update(formData); + /** @inheritdoc */ + _getSubmitData(updateData) { + let formData = super._getSubmitData(updateData); + formData = EntitySheetHelper.updateAttributes(formData, this.object); + formData = EntitySheetHelper.updateGroups(formData, this.object); + return formData; } - } diff --git a/module/actor.js b/module/actor.js index 3b667aa..9b7843a 100644 --- a/module/actor.js +++ b/module/actor.js @@ -1,14 +1,14 @@ import { EntitySheetHelper } from "./helper.js"; /** - * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. + * Extend the base Actor document to support attributes and groups with a custom template creation dialog. * @extends {Actor} */ export class SimpleActor extends Actor { - /** @override */ - prepareData() { - super.prepareData(); + /** @inheritdoc */ + prepareDerivedData() { + super.prepareDerivedData(); this.data.data.groups = this.data.data.groups || {}; this.data.data.attributes = this.data.data.attributes || {}; } @@ -16,8 +16,19 @@ export class SimpleActor extends Actor { /* -------------------------------------------- */ /** @override */ + static async createDialog(data={}, options={}) { + return EntitySheetHelper.createDialog.call(this, data, options); + } + + /* -------------------------------------------- */ + /* Roll Data Preparation */ + /* -------------------------------------------- */ + + /** @inheritdoc */ getRollData() { - const data = super.getRollData(); + + // Copy the actor's system data + const data = this.toObject(false).data; const shorthand = game.settings.get("worldbuilding", "macroShorthand"); const formulaAttributes = []; const itemAttributes = []; @@ -41,10 +52,11 @@ export class SimpleActor extends Actor { delete data.abil; delete data.groups; } - return data; } + /* -------------------------------------------- */ + /** * Apply shorthand syntax to actor roll data. * @param {Object} data The actor's data object. @@ -53,9 +65,9 @@ export class SimpleActor extends Actor { */ _applyShorthand(data, formulaAttributes, shorthand) { // Handle formula attributes when the short syntax is disabled. - for ( let [k, v] of Object.entries(data.attributes) ) { + for ( let [k, v] of Object.entries(data.attributes || {}) ) { // Make an array of formula attributes for later reference. - if ( v.dtype == "Formula" ) formulaAttributes.push(k); + if ( v.dtype === "Formula" ) formulaAttributes.push(k); // Add shortened version of the attributes. if ( !!shorthand ) { if ( !(k in data) ) { @@ -68,7 +80,7 @@ export class SimpleActor extends Actor { data[k] = {}; for ( let [gk, gv] of Object.entries(v) ) { data[k][gk] = gv.value; - if ( gv.dtype == "Formula" ) formulaAttributes.push(`${k}.${gk}`); + if ( gv.dtype === "Formula" ) formulaAttributes.push(`${k}.${gk}`); } } } @@ -76,22 +88,25 @@ export class SimpleActor extends Actor { } } + /* -------------------------------------------- */ + /** * Add items to the actor roll data object. Handles regular and shorthand * syntax, and calculates derived formula attributes on the items. * @param {Object} data The actor's data object. + * @param {string[]} itemAttributes * @param {Boolean} shorthand Whether or not the shorthand syntax is used. */ _applyItems(data, itemAttributes, shorthand) { // Map all items data using their slugified names - data.items = this.data.items.reduce((obj, i) => { - let key = i.name.slugify({strict: true}); - let itemData = duplicate(i.data); + data.items = this.items.reduce((obj, item) => { + const key = item.name.slugify({strict: true}); + const itemData = item.toObject(false).data; // Add items to shorthand and note which ones are formula attributes. for ( let [k, v] of Object.entries(itemData.attributes) ) { // When building the attribute list, prepend the item name for later use. - if ( v.dtype == "Formula" ) itemAttributes.push(`${key}..${k}`); + if ( v.dtype === "Formula" ) itemAttributes.push(`${key}..${k}`); // Add shortened version of the attributes. if ( !!shorthand ) { if ( !(k in itemData) ) { @@ -104,7 +119,7 @@ export class SimpleActor extends Actor { if ( !itemData[k] ) itemData[k] = {}; for ( let [gk, gv] of Object.entries(v) ) { itemData[k][gk] = gv.value; - if ( gv.dtype == "Formula" ) itemAttributes.push(`${key}..${k}.${gk}`); + if ( gv.dtype === "Formula" ) itemAttributes.push(`${key}..${k}.${gk}`); } } } @@ -115,7 +130,7 @@ export class SimpleActor extends Actor { if ( !itemData[k] ) itemData[k] = {}; for ( let [gk, gv] of Object.entries(v) ) { itemData[k][gk] = gv.value; - if ( gv.dtype == "Formula" ) itemAttributes.push(`${key}..${k}.${gk}`); + if ( gv.dtype === "Formula" ) itemAttributes.push(`${key}..${k}.${gk}`); } } } @@ -125,12 +140,13 @@ export class SimpleActor extends Actor { if ( !!shorthand ) { delete itemData.attributes; } - obj[key] = itemData; return obj; }, {}); } + /* -------------------------------------------- */ + _applyItemsFormulaReplacements(data, itemAttributes, shorthand) { for ( let k of itemAttributes ) { // Get the item name and separate the key. @@ -151,30 +167,32 @@ export class SimpleActor extends Actor { if ( !!shorthand ) { // Handle grouped attributes first. if ( data.items[item][k][gk] ) { - formula = data.items[item][k][gk]; - data.items[item][k][gk] = EntitySheetHelper.replaceData(formula.replace('@item.', `@items.${item}.`), data, {missing: "0"}); + formula = data.items[item][k][gk].replace('@item.', `@items.${item}.`); + data.items[item][k][gk] = Roll.replaceFormulaData(formula, data); } // Handle non-grouped attributes. else if ( data.items[item][k] ) { - formula = data.items[item][k]; - data.items[item][k] = EntitySheetHelper.replaceData(formula.replace('@item.', `@items.${item}.`), data, {missing: "0"}); + formula = data.items[item][k].replace('@item.', `@items.${item}.`); + data.items[item][k] = Roll.replaceFormulaData(formula, data); } } else { // Handle grouped attributes first. if ( data.items[item]['attributes'][k][gk] ) { - formula = data.items[item]['attributes'][k][gk]['value']; - data.items[item]['attributes'][k][gk]['value'] = EntitySheetHelper.replaceData(formula.replace('@item.', `@items.${item}.attributes.`), data, {missing: "0"}); + formula = data.items[item]['attributes'][k][gk]['value'].replace('@item.', `@items.${item}.attributes.`); + data.items[item]['attributes'][k][gk]['value'] = Roll.replaceFormulaData(formula, data); } // Handle non-grouped attributes. else if ( data.items[item]['attributes'][k]['value'] ) { - formula = data.items[item]['attributes'][k]['value']; - data.items[item]['attributes'][k]['value'] = EntitySheetHelper.replaceData(formula.replace('@item.', `@items.${item}.attributes.`), data, {missing: "0"}); + formula = data.items[item]['attributes'][k]['value'].replace('@item.', `@items.${item}.attributes.`); + data.items[item]['attributes'][k]['value'] = Roll.replaceFormulaData(formula, data); } } } } + /* -------------------------------------------- */ + /** * Apply replacements for derived formula attributes. * @param {Object} data The actor's data object. @@ -182,11 +200,9 @@ export class SimpleActor extends Actor { * @param {Boolean} shorthand Whether or not the shorthand syntax is used. */ _applyFormulaReplacements(data, formulaAttributes, shorthand) { - // Evaluate formula attributes after all other attributes have been handled, - // including items. + // Evaluate formula attributes after all other attributes have been handled, including items. for ( let k of formulaAttributes ) { - // Grouped attributes are included as `group.attr`, so we need to split - // them into new keys. + // Grouped attributes are included as `group.attr`, so we need to split them into new keys. let attr = null; if ( k.includes('.') ) { let attrKey = k.split('.'); @@ -195,15 +211,11 @@ export class SimpleActor extends Actor { } // Non-grouped attributes. if ( data.attributes[k]?.value ) { - data.attributes[k].value = EntitySheetHelper.replaceData(data.attributes[k].value, data, {missing: "0"}); - // TODO: Replace with: - // data.attributes[k].value = Roll.replaceFormulaData(data.attributes[k].value, data, {missing: "0"}); + data.attributes[k].value = Roll.replaceFormulaData(data.attributes[k].value, data); } // Grouped attributes. - else { - if ( attr ) { - data.attributes[k][attr].value = EntitySheetHelper.replaceData(data.attributes[k][attr].value, data, {missing: "0"}); - } + else if ( attr ) { + data.attributes[k][attr].value = Roll.replaceFormulaData(data.attributes[k][attr].value, data); } // Duplicate values to shorthand. diff --git a/module/helper.js b/module/helper.js index ccdf109..e4df457 100644 --- a/module/helper.js +++ b/module/helper.js @@ -3,7 +3,6 @@ import { ATTRIBUTE_TYPES } from "./constants.js"; export class EntitySheetHelper { static getAttributeData(data) { - data.dtypes = ATTRIBUTE_TYPES; // Determine attribute type. for ( let attr of Object.values(data.data.attributes) ) { @@ -61,7 +60,7 @@ export class EntitySheetHelper { // Add label fallback. if ( !gv.label ) gv.label = gk; // Add formula bool. - if ( gv.dtype == "Formula" ) { + if ( gv.dtype === "Formula" ) { gv.isFormula = true; } else { @@ -75,7 +74,7 @@ export class EntitySheetHelper { // Add label fallback. if ( !v.label ) v.label = k; // Add formula bool. - if ( v.dtype == "Formula" ) { + if ( v.dtype === "Formula" ) { v.isFormula = true; } else { @@ -94,7 +93,7 @@ export class EntitySheetHelper { // Closing the form/sheet will also trigger a submit, so only evaluate if this is an event. if ( event.currentTarget ) { // Exit early if this isn't a named attribute. - if ( event.currentTarget.tagName.toLowerCase() == 'input' && !event.currentTarget.hasAttribute('name')) { + if ( (event.currentTarget.tagName.toLowerCase() === 'input') && !event.currentTarget.hasAttribute('name')) { return false; } @@ -108,7 +107,7 @@ export class EntitySheetHelper { // Prevent attributes that already exist as groups. let groups = document.querySelectorAll('.group-key'); for ( let i = 0; i < groups.length; i++ ) { - if (groups[i].value == val) { + if (groups[i].value === val) { ui.notifications.error(game.i18n.localize("SIMPLE.NotifyAttrDuplicate") + ` (${val})`); el.value = oldVal; attrError = true; @@ -132,24 +131,21 @@ export class EntitySheetHelper { /** * Listen for click events on an attribute control to modify the composition of attributes in the sheet * @param {MouseEvent} event The originating left click event - * @private */ static async onClickAttributeControl(event) { event.preventDefault(); const a = event.currentTarget; const action = a.dataset.action; - - // Perform create and delete actions. switch ( action ) { case "create": - EntitySheetHelper.createAttribute(event, this); - break; + return EntitySheetHelper.createAttribute(event, this); case "delete": - EntitySheetHelper.deleteAttribute(event, this); - break; + return EntitySheetHelper.deleteAttribute(event, this); } } + /* -------------------------------------------- */ + /** * Listen for click events and modify attribute groups. * @param {MouseEvent} event The originating left click event @@ -158,14 +154,11 @@ export class EntitySheetHelper { event.preventDefault(); const a = event.currentTarget; const action = a.dataset.action; - switch ( action ) { case "create-group": - EntitySheetHelper.createAttributeGroup(event, this); - break; + return EntitySheetHelper.createAttributeGroup(event, this); case "delete-group": - EntitySheetHelper.deleteAttributeGroup(event, this); - break; + return EntitySheetHelper.deleteAttributeGroup(event, this); } } @@ -181,24 +174,24 @@ export class EntitySheetHelper { const label = button.closest(".attribute").querySelector(".attribute-label")?.value; const chatLabel = label ?? button.parentElement.querySelector(".attribute-key").value; const shorthand = game.settings.get("worldbuilding", "macroShorthand"); + // Use the actor for rollData so that formulas are always in reference to the parent actor. const rollData = this.actor.getRollData(); let formula = button.closest(".attribute").querySelector(".attribute-value")?.value; // If there's a formula, attempt to roll it. if ( formula ) { - // Get the machine safe version of the item name. let replacement = null; if ( formula.includes('@item.') && this.item ) { - let itemName = this.item.name.slugify({strict: true}); + let itemName = this.item.name.slugify({strict: true}); // Get the machine safe version of the item name. replacement = !!shorthand ? `@items.${itemName}.` : `@items.${itemName}.attributes.`; formula = formula.replace('@item.', replacement); } - formula = EntitySheetHelper.replaceData(formula, rollData, {missing: null}); - // Replace `@item` shorthand with the item name and make the roll. + + // Create the roll and the corresponding message let r = new Roll(formula, rollData); - r.roll().toMessage({ - user: game.user._id, + return r.toMessage({ + user: game.user.id, speaker: ChatMessage.getSpeaker({ actor: this.actor }), flavor: `${chatLabel}` }); @@ -218,7 +211,7 @@ export class EntitySheetHelper { */ static getAttributeHtml(items, index, group = false) { // Initialize the HTML. - let result = '
'; + let result = '
'; // Iterate over the supplied keys and build their inputs (including whether or not they need a group key). for (let [key, item] of Object.entries(items)) { result = result + ``; @@ -231,12 +224,13 @@ export class EntitySheetHelper { /** * Validate whether or not a group name can be used. - * @param {string} groupName Groupname to validate + * @param {string} groupName The candidate group name to validate + * @param {Document} document The Actor or Item instance within which the group is being defined * @returns {boolean} */ - static validateGroup(groupName, entity) { - let groups = Object.keys(entity.object.data.data.groups); - let attributes = Object.keys(entity.object.data.data.attributes).filter(a => !groups.includes(a)); + static validateGroup(groupName, document) { + let groups = Object.keys(document.data.data.groups || {}); + let attributes = Object.keys(document.data.data.attributes).filter(a => !groups.includes(a)); // Check for duplicate group keys. if ( groups.includes(groupName) ) { @@ -255,7 +249,6 @@ export class EntitySheetHelper { ui.notifications.error(game.i18n.localize("SIMPLE.NotifyGroupAlphanumeric")); return false; } - return true; } @@ -283,7 +276,7 @@ export class EntitySheetHelper { while ( objKeys.includes(newValue) ) { ++nk; newValue = `attr${nk}`; - }; + } // Build options for construction HTML inputs. let htmlItems = { @@ -364,7 +357,7 @@ export class EntitySheetHelper { const form = app.form; let newValue = $(a).siblings('.group-prefix').val(); // Verify the new group key is valid, and use it to create the group. - if ( newValue.length > 0 && EntitySheetHelper.validateGroup(newValue, app) ) { + if ( newValue.length > 0 && EntitySheetHelper.validateGroup(newValue, app.object) ) { let newKey = document.createElement("div"); newKey.innerHTML = ``; // Append the form element and submit the form. @@ -374,6 +367,8 @@ export class EntitySheetHelper { } } + /* -------------------------------------------- */ + /** * Delete an attribute group. * @param {MouseEvent} event The originating left click event @@ -410,15 +405,15 @@ export class EntitySheetHelper { /** * Update attributes when updating an actor object. - * - * @param {Object} formData Form data object to modify keys and values for. - * @returns {Object} updated formData object. + * @param {object} formData The form data object to modify keys and values for. + * @param {Document} document The Actor or Item document within which attributes are being updated + * @returns {object} The updated formData object. */ - static updateAttributes(formData, entity) { + static updateAttributes(formData, document) { let groupKeys = []; // Handle the free-form attributes list - const formAttrs = expandObject(formData).data.attributes || {}; + const formAttrs = foundry.utils.expandObject(formData)?.data?.attributes || {}; const attributes = Object.values(formAttrs).reduce((obj, v) => { let attrs = []; let group = null; @@ -453,14 +448,14 @@ export class EntitySheetHelper { }, {}); // Remove attributes which are no longer used - for ( let k of Object.keys(entity.object.data.data.attributes) ) { + for ( let k of Object.keys(document.data.data.attributes) ) { if ( !attributes.hasOwnProperty(k) ) attributes[`-=${k}`] = null; } // Remove grouped attributes which are no longer used. for ( let group of groupKeys) { - if ( entity.object.data.data.attributes[group] ) { - for ( let k of Object.keys(entity.object.data.data.attributes[group]) ) { + if ( document.data.data.attributes[group] ) { + for ( let k of Object.keys(document.data.data.attributes[group]) ) { if ( !attributes[group].hasOwnProperty(k) ) attributes[group][`-=${k}`] = null; } } @@ -470,18 +465,20 @@ export class EntitySheetHelper { formData = Object.entries(formData).filter(e => !e[0].startsWith("data.attributes")).reduce((obj, e) => { obj[e[0]] = e[1]; return obj; - }, {_id: entity.object._id, "data.attributes": attributes}); + }, {_id: document.id, "data.attributes": attributes}); return formData; } + /* -------------------------------------------- */ + /** * Update attribute groups when updating an actor object. - * - * @param {Object} formData Form data object to modify keys and values for. - * @returns {Object} updated formData object. + * @param {object} formData The form data object to modify keys and values for. + * @param {Document} document The Actor or Item document within which attributes are being updated + * @returns {object} The updated formData object. */ - static updateGroups(formData, entity) { + static updateGroups(formData, document) { // Handle the free-form groups list const formGroups = expandObject(formData).data.groups || {}; const groups = Object.values(formGroups).reduce((obj, v) => { @@ -498,7 +495,7 @@ export class EntitySheetHelper { }, {}); // Remove groups which are no longer used - for ( let k of Object.keys(entity.object.data.data.groups) ) { + for ( let k of Object.keys(document.data.data.groups) ) { if ( !groups.hasOwnProperty(k) ) groups[`-=${k}`] = null; } @@ -506,42 +503,71 @@ export class EntitySheetHelper { formData = Object.entries(formData).filter(e => !e[0].startsWith("data.groups")).reduce((obj, e) => { obj[e[0]] = e[1]; return obj; - }, {_id: entity.object._id, "data.groups": groups}); - + }, {_id: document.id, "data.groups": groups}); return formData; } /* -------------------------------------------- */ /** - * Replace referenced data attributes in the roll formula with the syntax `@attr` with the corresponding key from - * the provided `data` object. This is a temporary helper function that will be replaced with Roll.replaceFormulaData() - * in Foundry 0.7.1. - * - * @param {String} formula The original formula within which to replace. - * @param {Object} data Data object to use for value replacements. - * @param {Object} missing Value to use as missing replacements, such as {missing: "0"}. - * @return {String} The formula with attributes replaced with values. + * @see ClientDocumentMixin.createDialog */ - static replaceData(formula, data, {missing=null,depth=1}={}) { - // Exit early if the formula is invalid. - if ( typeof formula != "string" || depth < 1) { - return 0; - } - // Replace attributes with their numeric equivalents. - let dataRgx = new RegExp(/@([a-z.0-9_\-]+)/gi); - let rollFormula = formula.replace(dataRgx, (match, term) => { - let value = getProperty(data, term); - // If there was a value returned, trim and return it. - if ( value ) { - return String(value).trim(); - } - // Otherwise, return either the missing replacement value, or the original @attr string for later replacement. - else { - return missing != null ? missing : `@${term}`; - } - }); - return rollFormula; - } + static async createDialog(data={}, options={}) { + // Collect data + const documentName = this.metadata.name; + const folders = game.folders.filter(f => (f.data.type === documentName) && f.displayed); + const label = game.i18n.localize(this.metadata.label); + const title = game.i18n.format("ENTITY.Create", {entity: label}); + + // Identify the template Actor types + const collection = game.collections.get(this.documentName); + const templates = collection.filter(a => a.getFlag("worldbuilding", "isTemplate")); + const defaultType = this.metadata.types[0]; + const types = { + [defaultType]: game.i18n.localize("SIMPLE.NoTemplate") + } + for ( let a of templates ) { + types[a.id] = a.name; + } + + // Render the entity creation form + const html = await renderTemplate(`templates/sidebar/entity-create.html`, { + name: data.name || game.i18n.format("ENTITY.New", {entity: label}), + folder: data.folder, + folders: folders, + hasFolders: folders.length > 1, + type: data.type || templates[0]?.id || "", + types: types, + hasTypes: true + }); + + // Render the confirmation dialog window + return Dialog.prompt({ + title: title, + content: html, + label: title, + callback: html => { + + // Get the form data + const form = html[0].querySelector("form"); + const fd = new FormDataExtended(form); + let createData = fd.toObject(); + + // Merge with template data + const template = collection.get(form.type.value); + if ( template ) { + createData = foundry.utils.mergeObject(template.toObject(), createData); + createData.type = template.data.type; + delete createData.flags.worldbuilding.isTemplate; + } + + // Merge provided override data + createData = foundry.utils.mergeObject(createData, data); + return this.create(createData, {renderSheet: true}); + }, + rejectClose: false, + options: options + }); + } } \ No newline at end of file diff --git a/module/item-sheet.js b/module/item-sheet.js index 9d4a83c..fbe20ad 100644 --- a/module/item-sheet.js +++ b/module/item-sheet.js @@ -1,4 +1,5 @@ import { EntitySheetHelper } from "./helper.js"; +import {ATTRIBUTE_TYPES} from "./constants.js"; /** * Extend the basic ItemSheet with some very simple modifications @@ -6,9 +7,9 @@ import { EntitySheetHelper } from "./helper.js"; */ export class SimpleItemSheet extends ItemSheet { - /** @override */ + /** @inheritdoc */ static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { classes: ["worldbuilding", "sheet", "item"], template: "systems/worldbuilding/templates/item-sheet.html", width: 520, @@ -20,26 +21,30 @@ export class SimpleItemSheet extends ItemSheet { /* -------------------------------------------- */ - /** @override */ + /** @inheritdoc */ getData() { - const data = super.getData(); - EntitySheetHelper.getAttributeData(data); - return data; + const context = super.getData(); + EntitySheetHelper.getAttributeData(context.data); + context.systemData = context.data.data; + context.dtypes = ATTRIBUTE_TYPES; + return context; } /* -------------------------------------------- */ - /** @override */ + /** @inheritdoc */ activateListeners(html) { super.activateListeners(html); // Everything below here is only needed if the sheet is editable - if (!this.options.editable) return; + if ( !this.isEditable ) return; - // Rollable attributes + // Attribute Management + html.find(".attributes").on("click", ".attribute-control", EntitySheetHelper.onClickAttributeControl.bind(this)); + html.find(".groups").on("click", ".group-control", EntitySheetHelper.onClickAttributeGroupControl.bind(this)); html.find(".attributes").on("click", "a.attribute-roll", EntitySheetHelper.onAttributeRoll.bind(this)); - // Add draggable for macros. + // Add draggable for Macro creation html.find(".attributes a.attribute-roll").each((i, a) => { a.setAttribute("draggable", true); a.addEventListener("dragstart", ev => { @@ -47,24 +52,15 @@ export class SimpleItemSheet extends ItemSheet { ev.dataTransfer.setData('text/plain', JSON.stringify(dragData)); }, false); }); - - // Add or Remove Attribute - html.find(".attributes").on("click", ".attribute-control", EntitySheetHelper.onClickAttributeControl.bind(this)); - - // Add attribute groups. - html.find(".groups").on("click", ".group-control", EntitySheetHelper.onClickAttributeGroupControl.bind(this)); } /* -------------------------------------------- */ /** @override */ - _updateObject(event, formData) { - - // Handle attribute and group updates. - formData = EntitySheetHelper.updateAttributes(formData, this); - formData = EntitySheetHelper.updateGroups(formData, this); - - // Update the Actor with the new form values. - return this.object.update(formData); + _getSubmitData(updateData) { + let formData = super._getSubmitData(updateData); + formData = EntitySheetHelper.updateAttributes(formData, this.object); + formData = EntitySheetHelper.updateGroups(formData, this.object); + return formData; } } diff --git a/module/item.js b/module/item.js new file mode 100644 index 0000000..b4e0cce --- /dev/null +++ b/module/item.js @@ -0,0 +1,22 @@ +import {EntitySheetHelper} from "./helper.js"; + +/** + * Extend the base Item document to support attributes and groups with a custom template creation dialog. + * @extends {Item} + */ +export class SimpleItem extends Item { + + /** @inheritdoc */ + prepareDerivedData() { + super.prepareDerivedData(); + this.data.data.groups = this.data.data.groups || {}; + this.data.data.attributes = this.data.data.attributes || {}; + } + + /* -------------------------------------------- */ + + /** @override */ + static async createDialog(data={}, options={}) { + return EntitySheetHelper.createDialog.call(this, data, options); + } +} diff --git a/module/simple.js b/module/simple.js index 31e525f..0556c25 100644 --- a/module/simple.js +++ b/module/simple.js @@ -5,6 +5,7 @@ // Import Modules import { SimpleActor } from "./actor.js"; +import { SimpleItem } from "./item.js"; import { SimpleItemSheet } from "./item-sheet.js"; import { SimpleActorSheet } from "./actor-sheet.js"; import { preloadHandlebarsTemplates } from "./templates.js"; @@ -35,7 +36,8 @@ Hooks.once("init", async function() { }; // Define custom Entity classes - CONFIG.Actor.entityClass = SimpleActor; + CONFIG.Actor.documentClass = SimpleActor; + CONFIG.Item.documentClass = SimpleItem; // Register sheet application classes Actors.unregisterSheet("core", ActorSheet); @@ -74,21 +76,12 @@ Hooks.once("init", async function() { * @param {boolean} notify - Whether or not to post nofications. */ function _simpleUpdateInit(formula, notify = false) { - // If the formula is valid, use it. - try { - new Roll(formula).roll(); - CONFIG.Combat.initiative.formula = formula; - if (notify) { - ui.notifications.notify(game.i18n.localize("SIMPLE.NotifyInitFormulaUpdated") + ` ${formula}`); - } - } - // Otherwise, fall back to a d20. - catch (error) { - CONFIG.Combat.initiative.formula = "1d20"; - if (notify) { - ui.notifications.error(game.i18n.localize("SIMPLE.NotifyInitFormulaInvalid") + ` ${formula}`); - } + const isValid = Roll.validate(formula); + if ( !isValid ) { + if ( notify ) ui.notifications.error(`${game.i18n.localize("SIMPLE.NotifyInitFormulaInvalid")}: ${formula}`); + return; } + CONFIG.Combat.initiative.formula = formula; } /** @@ -98,8 +91,8 @@ Hooks.once("init", async function() { return value.slugify({strict: true}); }); - // Preload template partials. - preloadHandlebarsTemplates(); + // Preload template partials + await preloadHandlebarsTemplates(); }); /** @@ -172,68 +165,3 @@ Hooks.on("getItemDirectoryEntryContext", (html, options) => { } }); }); - - -async function _onCreateEntity(event) { - event.preventDefault(); - event.stopPropagation(); - return _simpleDirectoryTemplates(this, event); -} -ActorDirectory.prototype._onCreateEntity = _onCreateEntity; // For 0.7.x+ -ItemDirectory.prototype._onCreateEntity = _onCreateEntity; -ActorDirectory.prototype._onCreate = _onCreateEntity; // TODO: for 0.6.6 -ItemDirectory.prototype._onCreate = _onCreateEntity; - -/** - * Display the entity template dialog. - * - * Helper function to display a dialog if there are multiple template types defined for the entity type. - * TODO: Refactor in 0.7.x to play more nicely with the Entity.createDialog method - *1 - * @param {EntityCollection} entityType - The sidebar tab - * @param {MouseEvent} event - Triggering event - */ -async function _simpleDirectoryTemplates(collection, event) { - - // Retrieve the collection and find any available templates - const entityCollection = collection.tabName === "actors" ? game.actors : game.items; - const cls = collection.tabName === "actors" ? Actor : Item; - let templates = entityCollection.filter(a => a.getFlag("worldbuilding", "isTemplate")); - let ent = game.i18n.localize(cls.config.label); - - // Setup default creation data - let type = collection.tabName === "actors" ? 'character' : 'item'; - let createData = { - name: `${game.i18n.localize("SIMPLE.New")} ${ent}`, - type: type, - folder: event.currentTarget.dataset.folder - }; - if ( !templates.length ) return cls.create(createData, {renderSheet: true}); - - // Build an array of types for the form, including an empty default. - let types = [{ - value: null, - label: game.i18n.localize("SIMPLE.NoTemplate") - }].concat(templates.map(a => { return { value: a.id, label: a.name } })); - - // Render the confirmation dialog window - const templateData = {upper: ent, lower: ent.toLowerCase(), types: types}; - const dlg = await renderTemplate(`systems/worldbuilding/templates/sidebar/entity-create.html`, templateData); - return Dialog.confirm({ - title: `${game.i18n.localize("SIMPLE.Create")} ${createData.name}`, - content: dlg, - yes: html => { - const form = html[0].querySelector("form"); - const template = entityCollection.get(form.type.value); - if ( template ) { - createData = mergeObject(template.data, createData, {inplace: false}); - createData.type = template.data.type; - delete createData.flags.worldbuilding.isTemplate; - } - createData.name = form.name.value; - return cls.create(createData, {renderSheet: true}); - }, - no: () => {}, - defaultYes: false - }); -} \ No newline at end of file diff --git a/system.json b/system.json index 1ba9387..fb57af9 100644 --- a/system.json +++ b/system.json @@ -2,10 +2,9 @@ "name": "worldbuilding", "title": "Simple World-Building", "description": "A minimalist game system which provides configurable Actor and Item templates to support free-form system agnostic game-play.", - "version": 0.40, - "minimumCoreVersion": "0.6.6", - "compatibleCoreVersion": "0.7.3", - "templateVersion": 2, + "version": "0.5.0", + "minimumCoreVersion": "0.8.4", + "compatibleCoreVersion": "0.8.6", "author": "Atropos", "esmodules": ["module/simple.js"], "styles": ["styles/simple.css"], @@ -22,7 +21,7 @@ "primaryTokenAttribute": "health", "secondaryTokenAttribute": "power", "url": "https://gitlab.com/foundrynet/worldbuilding/", - "manifest": "https://gitlab.com/foundrynet/worldbuilding/raw/master/system.json", - "download": "https://gitlab.com/foundrynet/worldbuilding/-/archive/release-040/worldbuilding-release-040.zip", + "manifest": "https://gitlab.com/foundrynet/worldbuilding/raw/0.5.x/system.json", + "download": "https://gitlab.com/foundrynet/worldbuilding/-/archive/release-050/worldbuilding-release-050.zip", "license": "LICENSE.txt" } diff --git a/templates/actor-sheet.html b/templates/actor-sheet.html index d0795d1..7979f17 100644 --- a/templates/actor-sheet.html +++ b/templates/actor-sheet.html @@ -2,18 +2,20 @@ {{!-- Sheet Header --}}
- +
-

+

+ +

- + / - +
- + / - +
@@ -29,14 +31,14 @@
{{!-- Biography Tab --}} -
- {{editor content=data.biography target="data.biography" button=true owner=owner editable=editable}} +
+ {{editor content=systemData.biography target="data.biography" button=true owner=owner editable=editable rollData=rollData}}
{{!-- Owned Items Tab --}}
    - {{#each actor.items as |item id|}} + {{#each data.items as |item id|}}
  1. {{item.name}}

    @@ -75,12 +77,15 @@ {{/each}}
- - + +
{{/each}} +

+ {{ localize "SIMPLE.ItemCreate" }} +

{{!-- Attributes Tab --}} @@ -94,14 +99,14 @@ {{!-- Render the attribute list partial. --}} - {{> "systems/worldbuilding/templates/parts/sheet-attributes.html" attributes=data.ungroupedAttributes dtypes=dtypes}} + {{> "systems/worldbuilding/templates/parts/sheet-attributes.html" attributes=systemData.ungroupedAttributes dtypes=dtypes}} {{!-- Render the grouped attributes partial and control. --}}
- {{> "systems/worldbuilding/templates/parts/sheet-groups.html" attributes=data.groupedAttributes groups=data.groups dtypes=dtypes}} + {{> "systems/worldbuilding/templates/parts/sheet-groups.html" attributes=systemData.groupedAttributes groups=systemData.groups dtypes=dtypes}}
diff --git a/templates/item-sheet.html b/templates/item-sheet.html index 6e6524b..0909121 100644 --- a/templates/item-sheet.html +++ b/templates/item-sheet.html @@ -1,15 +1,17 @@
- +
-

+

+ +

- +
- +
@@ -25,7 +27,7 @@ {{!-- Description Tab --}}
- {{editor content=data.description target="data.description" button=true owner=owner editable=editable}} + {{editor content=systemData.description target="data.description" button=true owner=owner editable=editable rollData=rollData}}
{{!-- Attributes Tab --}} @@ -39,14 +41,13 @@ {{!-- Render the attribute list partial. --}} - {{> "systems/worldbuilding/templates/parts/sheet-attributes.html" attributes=data.ungroupedAttributes dtypes=dtypes}} + {{> "systems/worldbuilding/templates/parts/sheet-attributes.html" attributes=systemData.ungroupedAttributes dtypes=dtypes}} {{!-- Render the grouped attributes partial and control. --}}
- {{> "systems/worldbuilding/templates/parts/sheet-groups.html" attributes=data.groupedAttributes groups=data.groups dtypes=dtypes}} - + {{> "systems/worldbuilding/templates/parts/sheet-groups.html" attributes=systemData.groupedAttributes groups=systemData.groups dtypes=dtypes}}
diff --git a/templates/sidebar/entity-create.html b/templates/sidebar/entity-create.html deleted file mode 100644 index 19bcb29..0000000 --- a/templates/sidebar/entity-create.html +++ /dev/null @@ -1,15 +0,0 @@ - -
- - -
- -
- - -
-