Merge branch '9-actor-templates-integration' into 'master'

9: Add actor templates

See merge request foundrynet/worldbuilding!8
This commit is contained in:
Andrew 2020-08-31 02:09:21 +00:00
commit c515f8d5b7
10 changed files with 270 additions and 85 deletions

View file

@ -9,5 +9,9 @@
"SIMPLE.ResourceMin": "Min", "SIMPLE.ResourceMin": "Min",
"SIMPLE.ResourceValue": "Value", "SIMPLE.ResourceValue": "Value",
"SIMPLE.ResourceMax": "Max" "SIMPLE.ResourceMax": "Max",
"SIMPLE.DefineTemplate": "Define as Template",
"SIMPLE.UnsetTemplate": "Unset Template",
"SIMPLE.NoTemplate": "No Template"
} }

View file

@ -1,3 +1,5 @@
import { ATTRIBUTE_TYPES } from "./constants.js";
/** /**
* Extend the basic ActorSheet with some very simple modifications * Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet} * @extends {ActorSheet}
@ -21,7 +23,7 @@ export class SimpleActorSheet extends ActorSheet {
/** @override */ /** @override */
getData() { getData() {
const data = super.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) ) { for ( let attr of Object.values(data.data.attributes) ) {
attr.isCheckbox = attr.dtype === "Boolean"; attr.isCheckbox = attr.dtype === "Boolean";
attr.isResource = attr.dtype === "Resource"; attr.isResource = attr.dtype === "Resource";

1
module/constants.js Normal file
View file

@ -0,0 +1 @@
export const ATTRIBUTE_TYPES = ["String", "Number", "Boolean", "Formula", "Resource"];

View file

@ -1,3 +1,5 @@
import { ATTRIBUTE_TYPES } from "./constants.js";
/** /**
* Extend the basic ItemSheet with some very simple modifications * Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet} * @extends {ItemSheet}
@ -20,7 +22,7 @@ export class SimpleItemSheet extends ItemSheet {
/** @override */ /** @override */
getData() { getData() {
const data = super.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) ) { for ( let attr of Object.values(data.data.attributes) ) {
attr.isCheckbox = attr.dtype === "Boolean"; attr.isCheckbox = attr.dtype === "Boolean";
attr.isResource = attr.dtype === "Resource"; attr.isResource = attr.dtype === "Resource";

View file

@ -8,6 +8,7 @@
import { SimpleActor } from "./actor.js"; import { SimpleActor } from "./actor.js";
import { SimpleItemSheet } from "./item-sheet.js"; import { SimpleItemSheet } from "./item-sheet.js";
import { SimpleActorSheet } from "./actor-sheet.js"; import { SimpleActorSheet } from "./actor-sheet.js";
import { preloadHandlebarsTemplates } from "./templates.js";
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Foundry VTT Initialization */ /* Foundry VTT Initialization */
@ -89,4 +90,184 @@ Hooks.once("init", async function() {
return value.slugify({strict: true}); 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: '<i class="fas fa-stamp"></i>',
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: '<i class="fas fa-times"></i>',
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: '<i class="fas fa-stamp"></i>',
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: '<i class="fas fa-times"></i>',
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: '<i class="fas fa-check"></i>',
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});
}
}

16
module/templates.js Normal file
View file

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

View file

@ -73,47 +73,8 @@
<a class="attribute-control" data-action="create"><i class="fas fa-plus"></i></a> <a class="attribute-control" data-action="create"><i class="fas fa-plus"></i></a>
</header> </header>
<ol class="attributes-list"> {{!-- Render the attribute list partial. --}}
{{#each data.attributes as |attr key|}} {{> "systems/worldbuilding/templates/parts/sheet-attributes.html" attributes=data.attributes dtypes=dtypes}}
<li class="attribute flexrow" data-attribute="{{key}}">
<input class="attribute-key" type="text" name="data.attributes.{{key}}.key" value="{{key}}"/>
{{!-- Handle booleans. --}}
{{#if attr.isCheckbox}}
<label class="attribute-value checkbox"><input type="checkbox" name="data.attributes.{{key}}.value" {{checked attr.value}}/></label>
{{else}}
{{!-- Handle resources. --}}
{{#if attr.isResource}}
<div class="attribute-group flexrow">
<span class="attribute-col flexcol">
<label for="data.attributes.{{key}}.min">{{localize "SIMPLE.ResourceMin"}}</label>
<input class="attribute-value" type="text" name="data.attributes.{{key}}.min" value="{{attr.min}}" data-dtype="{{attr.dtype}}"/>
</span>
<span class="attribute-col flexcol">
<label for="data.attributes.{{key}}.value">{{localize "SIMPLE.ResourceValue"}}</label>
<input class="attribute-value" type="text" name="data.attributes.{{key}}.value" value="{{attr.value}}" data-dtype="{{attr.dtype}}"/>
</span>
<span class="attribute-col flexcol">
<label for="data.attributes.{{key}}.max">{{localize "SIMPLE.ResourceMax"}}</label>
<input class="attribute-value" type="text" name="data.attributes.{{key}}.max" value="{{attr.max}}" data-dtype="{{attr.dtype}}"/>
</span>
</div>
{{!-- Handle other input types. --}}
{{else}}
<input class="attribute-value" type="text" name="data.attributes.{{key}}.value" value="{{attr.value}}" data-dtype="{{attr.dtype}}"/>
{{/if}}
{{/if}}
<input class="attribute-label" type="text" name="data.attributes.{{key}}.label" value="{{attr.label}}"/>
<select class="attribute-dtype" name="data.attributes.{{key}}.dtype">
{{#select attr.dtype}}
{{#each ../dtypes as |t|}}
<option value="{{t}}">{{t}}</option>
{{/each}}
{{/select}}
</select>
<a class="attribute-control" data-action="delete"><i class="fas fa-trash"></i></a>
</li>
{{/each}}
</ol>
</div> </div>
</section> </section>
</form> </form>

View file

@ -38,47 +38,8 @@
<a class="attribute-control" data-action="create"><i class="fas fa-plus"></i></a> <a class="attribute-control" data-action="create"><i class="fas fa-plus"></i></a>
</header> </header>
<ol class="attributes-list"> {{!-- Render the attribute list partial. --}}
{{#each data.attributes as |attr key|}} {{> "systems/worldbuilding/templates/parts/sheet-attributes.html" attributes=data.attributes dtypes=dtypes}}
<li class="attribute flexrow" data-attribute="{{key}}">
<input class="attribute-key" type="text" name="data.attributes.{{key}}.key" value="{{key}}"/>
{{!-- Handle booleans. --}}
{{#if attr.isCheckbox}}
<label class="attribute-value checkbox"><input type="checkbox" name="data.attributes.{{key}}.value" {{checked attr.value}}/></label>
{{else}}
{{!-- Handle resources. --}}
{{#if attr.isResource}}
<div class="attribute-group flexrow">
<span class="attribute-col flexcol">
<label for="data.attributes.{{key}}.min">Min</label>
<input class="attribute-value" type="text" name="data.attributes.{{key}}.min" value="{{attr.min}}" data-dtype="{{attr.dtype}}"/>
</span>
<span class="attribute-col flexcol">
<label for="data.attributes.{{key}}.value">Current</label>
<input class="attribute-value" type="text" name="data.attributes.{{key}}.value" value="{{attr.value}}" data-dtype="{{attr.dtype}}"/>
</span>
<span class="attribute-col flexcol">
<label for="data.attributes.{{key}}.max">Max</label>
<input class="attribute-value" type="text" name="data.attributes.{{key}}.max" value="{{attr.max}}" data-dtype="{{attr.dtype}}"/>
</span>
</div>
{{!-- Handle other input types. --}}
{{else}}
<input class="attribute-value" type="text" name="data.attributes.{{key}}.value" value="{{attr.value}}" data-dtype="{{attr.dtype}}"/>
{{/if}}
{{/if}}
<input class="attribute-label" type="text" name="data.attributes.{{key}}.label" value="{{attr.label}}"/>
<select class="attribute-dtype" name="data.attributes.{{key}}.dtype">
{{#select attr.dtype}}
{{#each ../dtypes as |t|}}
<option value="{{t}}">{{t}}</option>
{{/each}}
{{/select}}
</select>
<a class="attribute-control" data-action="delete"><i class="fas fa-trash"></i></a>
</li>
{{/each}}
</ol>
</div> </div>
</section> </section>
</form> </form>

View file

@ -0,0 +1,41 @@
<ol class="attributes-list">
{{#each attributes as |attr key|}}
<li class="attribute flexrow" data-attribute="{{key}}">
<input class="attribute-key" type="text" name="data.attributes.{{key}}.key" value="{{key}}"/>
{{!-- Handle booleans. --}}
{{#if attr.isCheckbox}}
<label class="attribute-value checkbox"><input type="checkbox" name="data.attributes.{{key}}.value" {{checked attr.value}}/></label>
{{else}}
{{!-- Handle resources. --}}
{{#if attr.isResource}}
<div class="attribute-group flexrow">
<span class="attribute-col flexcol">
<label for="data.attributes.{{key}}.min">{{localize "SIMPLE.ResourceMin"}}</label>
<input class="attribute-value" type="text" name="data.attributes.{{key}}.min" value="{{attr.min}}" data-dtype="{{attr.dtype}}"/>
</span>
<span class="attribute-col flexcol">
<label for="data.attributes.{{key}}.value">{{localize "SIMPLE.ResourceValue"}}</label>
<input class="attribute-value" type="text" name="data.attributes.{{key}}.value" value="{{attr.value}}" data-dtype="{{attr.dtype}}"/>
</span>
<span class="attribute-col flexcol">
<label for="data.attributes.{{key}}.max">{{localize "SIMPLE.ResourceMax"}}</label>
<input class="attribute-value" type="text" name="data.attributes.{{key}}.max" value="{{attr.max}}" data-dtype="{{attr.dtype}}"/>
</span>
</div>
{{!-- Handle other input types. --}}
{{else}}
<input class="attribute-value" type="text" name="data.attributes.{{key}}.value" value="{{attr.value}}" data-dtype="{{attr.dtype}}"/>
{{/if}}
{{/if}}
<input class="attribute-label" type="text" name="data.attributes.{{key}}.label" value="{{attr.label}}"/>
<select class="attribute-dtype" name="data.attributes.{{key}}.dtype">
{{#select attr.dtype}}
{{#each ../dtypes as |t|}}
<option value="{{t}}">{{t}}</option>
{{/each}}
{{/select}}
</select>
<a class="attribute-control" data-action="delete"><i class="fas fa-trash"></i></a>
</li>
{{/each}}
</ol>

View file

@ -0,0 +1,16 @@
<form id="entity-create" autocomplete="off" onsubmit="event.preventDefault();">
<div class="form-group">
<label>{{localize "Name"}}</label>
<input type="text" name="name" placeholder="{{localize 'ENTITY.CreateNew'}} {{upper}}"/>
</div>
<div class="form-group">
<label>{{localize "Type"}}</label>
<select name="type">
{{#each types}}
<option value="{{this.value}}">{{this.label}}</option>
{{/each}}
</select>
<p class="notes">{{localize "ENTITY.TypeHint"}}</p>
</div>
</form>