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 --}}
-
+