diff --git a/lang/en.json b/lang/en.json index 9ae578d..f3cf520 100644 --- a/lang/en.json +++ b/lang/en.json @@ -9,5 +9,9 @@ "SIMPLE.ResourceMin": "Min", "SIMPLE.ResourceValue": "Value", - "SIMPLE.ResourceMax": "Max" + "SIMPLE.ResourceMax": "Max", + + "SIMPLE.DefineTemplate": "Define as Template", + "SIMPLE.UnsetTemplate": "Unset Template", + "SIMPLE.NoTemplate": "No Template" } \ No newline at end of file diff --git a/module/actor-sheet.js b/module/actor-sheet.js index 86a97b1..2a42379 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -1,3 +1,5 @@ +import { ATTRIBUTE_TYPES } from "./constants.js"; + /** * Extend the basic ActorSheet with some very simple modifications * @extends {ActorSheet} @@ -21,7 +23,7 @@ export class SimpleActorSheet extends ActorSheet { /** @override */ getData() { const data = super.getData(); - data.dtypes = ["String", "Number", "Boolean", "Formula", "Resource"]; + data.dtypes = ATTRIBUTE_TYPES; for ( let attr of Object.values(data.data.attributes) ) { attr.isCheckbox = attr.dtype === "Boolean"; attr.isResource = attr.dtype === "Resource"; diff --git a/module/constants.js b/module/constants.js new file mode 100644 index 0000000..f2f79f6 --- /dev/null +++ b/module/constants.js @@ -0,0 +1 @@ +export const ATTRIBUTE_TYPES = ["String", "Number", "Boolean", "Formula", "Resource"]; \ No newline at end of file diff --git a/module/item-sheet.js b/module/item-sheet.js index 44ad27b..fed2f96 100644 --- a/module/item-sheet.js +++ b/module/item-sheet.js @@ -1,3 +1,5 @@ +import { ATTRIBUTE_TYPES } from "./constants.js"; + /** * Extend the basic ItemSheet with some very simple modifications * @extends {ItemSheet} @@ -20,7 +22,7 @@ export class SimpleItemSheet extends ItemSheet { /** @override */ getData() { const data = super.getData(); - data.dtypes = ["String", "Number", "Boolean", "Formula", "Resource"]; + data.dtypes = ATTRIBUTE_TYPES; for ( let attr of Object.values(data.data.attributes) ) { attr.isCheckbox = attr.dtype === "Boolean"; attr.isResource = attr.dtype === "Resource"; diff --git a/module/simple.js b/module/simple.js index d618abc..e2a04df 100644 --- a/module/simple.js +++ b/module/simple.js @@ -8,6 +8,7 @@ import { SimpleActor } from "./actor.js"; import { SimpleItemSheet } from "./item-sheet.js"; import { SimpleActorSheet } from "./actor-sheet.js"; +import { preloadHandlebarsTemplates } from "./templates.js"; /* -------------------------------------------- */ /* Foundry VTT Initialization */ @@ -89,4 +90,184 @@ Hooks.once("init", async function() { return value.slugify({strict: true}); }); + // Preload template partials. + preloadHandlebarsTemplates(); }); + +/** + * Adds the actor template context menu. + */ +Hooks.on("getActorDirectoryEntryContext", (html, options) => { + // Define an actor as a template. + options.push({ + name: game.i18n.localize("SIMPLE.DefineTemplate"), + icon: '', + condition: li => { + const actor = game.actors.get(li.data("entityId")); + return !actor.getFlag("worldbuilding", "isTemplate"); + }, + callback: li => { + const actor = game.actors.get(li.data("entityId")); + actor.setFlag("worldbuilding", "isTemplate", true); + } + }); + + // Undefine an actor as a template. + options.push({ + name: game.i18n.localize("SIMPLE.UnsetTemplate"), + icon: '', + condition: li => { + const actor = game.actors.get(li.data("entityId")); + return actor.getFlag("worldbuilding", "isTemplate"); + }, + callback: li => { + const actor = game.actors.get(li.data("entityId")); + actor.setFlag("worldbuilding", "isTemplate", false); + } + }); +}); + +/** + * Adds the item template context menu. + */ +Hooks.on("getItemDirectoryEntryContext", (html, options) => { + // Define an item as a template. + options.push({ + name: game.i18n.localize("SIMPLE.DefineTemplate"), + icon: '', + condition: li => { + const item = game.items.get(li.data("entityId")); + return !item.getFlag("worldbuilding", "isTemplate"); + }, + callback: li => { + const item = game.items.get(li.data("entityId")); + item.setFlag("worldbuilding", "isTemplate", true); + } + }); + + // Undefine an item as a template. + options.push({ + name: game.i18n.localize("SIMPLE.UnsetTemplate"), + icon: '', + condition: li => { + const item = game.items.get(li.data("entityId")); + return item.getFlag("worldbuilding", "isTemplate"); + }, + callback: li => { + const item = game.items.get(li.data("entityId")); + item.setFlag("worldbuilding", "isTemplate", false); + } + }); +}); + + +/** + * Adds the actor template selection dialog. + */ +ActorDirectory.prototype._onCreate = async (event) => { + // Do not allow the creation event to bubble to other listeners + event.preventDefault(); + event.stopPropagation(); + + _simpleDirectoryTemplates('actor'); +} + +/** + * Adds the item template selection dialog. + */ +ItemDirectory.prototype._onCreate = async (event) => { + // Do not allow the creation event to bubble to other listeners + event.preventDefault(); + event.stopPropagation(); + + _simpleDirectoryTemplates('item'); +} + +/** + * Display the entity template dialog. + * + * Helper function to display a dialog if there are multiple template types + * defined for the entity type. + * + * @param {string} entityType - 'actor' or 'item' + */ +async function _simpleDirectoryTemplates(entityType = 'actor') { + // Retrieve the collection and class. + const entityCollection = entityType == 'actor' ? game.actors : game.items; + const cls = entityType == 'actor' ? Actor : Item; + + // Query for all entities of this type using the "isTemplate" flag. + let entities = entityCollection.filter(a => a.data.flags?.worldbuilding?.isTemplate === true); + + // Initialize variables related to the entity class. + let ent = game.i18n.localize(cls.config.label); + + // Setup entity data. + let type = entityType == 'actor' ? 'character' : 'item'; + let createData = { + name: `New ${ent}`, + type: type, + folder: event.currentTarget.dataset.folder + }; + + // If there's more than one entity template type, create a form. + if (entities.length > 0) { + // Build an array of types for the form, including an empty default. + let types = [{ + value: null, + label: game.i18n.localize("SIMPLE.NoTemplate") + }]; + + // Append each of the user-defined actor/item types. + types = types.concat(entities.map(a => { + return { + value: a.data.name, + label: a.data.name + } + })); + + // Render the entity creation form + let templateData = {upper: ent, lower: ent.toLowerCase(), types: types}, + dlg = await renderTemplate(`systems/worldbuilding/templates/sidebar/entity-create.html`, templateData); + + // Render the confirmation dialog window + new Dialog({ + title: `Create ${createData.name}`, + content: dlg, + buttons: { + create: { + icon: '', + label: `Create ${ent}`, + callback: html => { + // Get the form data. + const form = html[0].querySelector("form"); + mergeObject(createData, validateForm(form)); + + // Store the type and name values, and retrieve the template entity. + let templateActor = entityCollection.getName(createData.type); + + // If there's a template entity, handle the data. + if (templateActor) { + // Update the object with the existing template's values. + createData = mergeObject(templateActor.data, createData, {inplace: false}); + createData.type = templateActor.data.type; + // Clear the flag so that this doesn't become a new template. + delete createData.flags.worldbuilding.isTemplate; + } + // Otherwise, restore to a valid entity type (character/item). + else { + createData.type = type; + } + + cls.create(createData, {renderSheet: true}); + } + } + }, + default: "create" + }).render(true); + } + // Otherwise, just create a blank entity. + else { + cls.create(createData, {renderSheet: true}); + } +} \ No newline at end of file diff --git a/module/templates.js b/module/templates.js new file mode 100644 index 0000000..840f4f0 --- /dev/null +++ b/module/templates.js @@ -0,0 +1,16 @@ +/** + * Define a set of template paths to pre-load + * Pre-loaded templates are compiled and cached for fast access when rendering + * @return {Promise} + */ +export const preloadHandlebarsTemplates = async function() { + + // Define template paths to load + const templatePaths = [ + // Attribute list partial. + "systems/worldbuilding/templates/parts/sheet-attributes.html" + ]; + + // Load the template parts + return loadTemplates(templatePaths); +}; \ No newline at end of file diff --git a/templates/actor-sheet.html b/templates/actor-sheet.html index 555bb86..51656d8 100644 --- a/templates/actor-sheet.html +++ b/templates/actor-sheet.html @@ -73,47 +73,8 @@ -