Tidy the worldbuilding system for 0.40 release

This commit is contained in:
Andrew 2020-10-19 17:07:23 -07:00
parent d641ff96b0
commit 6bf056b3d4
9 changed files with 72 additions and 184 deletions

View file

@ -24,11 +24,7 @@ export class SimpleActorSheet extends ActorSheet {
/** @override */ /** @override */
getData() { getData() {
const data = super.getData(); const data = super.getData();
// Handle attribute groups.
EntitySheetHelper.getAttributeData(data); EntitySheetHelper.getAttributeData(data);
// Add shorthand.
data.shorthand = !!game.settings.get("worldbuilding", "macroShorthand"); data.shorthand = !!game.settings.get("worldbuilding", "macroShorthand");
return data; return data;
} }
@ -39,15 +35,13 @@ export class SimpleActorSheet extends ActorSheet {
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
// Handle rollable items.
html.find(".items .rollable").on("click", this._onItemRoll.bind(this));
// Handle rollable attributes.
html.find(".attributes").on("click", "a.attribute-roll", EntitySheetHelper.onAttributeRoll.bind(this));
// Everything below here is only needed if the sheet is editable // Everything below here is only needed if the sheet is editable
if ( !this.options.editable ) return; if ( !this.options.editable ) return;
// Handle rollable items and attributes
html.find(".items .rollable").on("click", this._onItemRoll.bind(this));
html.find(".attributes").on("click", "a.attribute-roll", EntitySheetHelper.onAttributeRoll.bind(this));
// Update Inventory Item // Update Inventory Item
html.find('.item-edit').click(ev => { html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item"); const li = $(ev.currentTarget).parents(".item");
@ -80,36 +74,6 @@ export class SimpleActorSheet extends ActorSheet {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */
async _onSubmit(event, {updateData=null, preventClose=false, preventRender=false}={}) {
let attr = EntitySheetHelper.onSubmit(event);
// Submit the form if attr is true or an attr key.
if ( attr ) {
await super._onSubmit(event, {updateData: updateData, preventClose: preventClose, preventRender: preventRender});
// If attr is a key and not just true, set a very short timeout and retrigger focus after the original element is deleted and the new one is inserted.
if ( attr !== true) {
setTimeout(() => {
$(`input[name="${attr}"]`).parents('.attribute').find('.attribute-value').focus();
}, 10);
}
}
}
/* -------------------------------------------- */
/** @override */
setPosition(options={}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
/** /**
* Listen for roll buttons on items. * Listen for roll buttons on items.
* @param {MouseEvent} event The originating left click event * @param {MouseEvent} event The originating left click event
@ -126,14 +90,12 @@ export class SimpleActorSheet extends ActorSheet {
}); });
} }
/* -------------------------------------------- */
/** @override */ /** @override */
_updateObject(event, formData) { _updateObject(event, formData) {
// Handle attribute and group updates.
formData = EntitySheetHelper.updateAttributes(formData, this); formData = EntitySheetHelper.updateAttributes(formData, this);
formData = EntitySheetHelper.updateGroups(formData, this); formData = EntitySheetHelper.updateGroups(formData, this);
// Update the Actor with the new form values.
return this.object.update(formData); return this.object.update(formData);
} }

View file

@ -181,7 +181,7 @@ export class EntitySheetHelper {
const label = button.closest(".attribute").querySelector(".attribute-label")?.value; const label = button.closest(".attribute").querySelector(".attribute-label")?.value;
const chatLabel = label ?? button.parentElement.querySelector(".attribute-key").value; const chatLabel = label ?? button.parentElement.querySelector(".attribute-key").value;
const shorthand = game.settings.get("worldbuilding", "macroShorthand"); const shorthand = game.settings.get("worldbuilding", "macroShorthand");
const rollData = this.actor.getRollData(); const rollData = this.object.getRollData();
let formula = button.closest(".attribute").querySelector(".attribute-value")?.value; let formula = button.closest(".attribute").querySelector(".attribute-value")?.value;
// If there's a formula, attempt to roll it. // If there's a formula, attempt to roll it.
@ -527,18 +527,14 @@ export class EntitySheetHelper {
if ( typeof formula != "string" || depth < 1) { if ( typeof formula != "string" || depth < 1) {
return 0; return 0;
} }
// Replace attributes with their numeric equivalents. // Replace attributes with their numeric equivalents.
let dataRgx = new RegExp(/@([a-z.0-9_\-]+)/gi); let dataRgx = new RegExp(/@([a-z.0-9_\-]+)/gi);
let rollFormula = formula.replace(dataRgx, (match, term) => { let rollFormula = formula.replace(dataRgx, (match, term) => {
// Replace matches with the value, or the missing value.
let value = getProperty(data, term); let value = getProperty(data, term);
value = value ? String(value).trim() : (missing != null ? missing : `@${term}`); if ( value === null ) return "0";
// If there's still an attribute in the returned string, nest it in parentheses so that it's evaluated first in the roll. if ( String(value).includes('@') ) return value;
value = value && value.includes('@') ? `(${value})` : value; else return `@${term}`;
return value;
}); });
return rollFormula; return rollFormula;
} }

View file

@ -23,55 +23,22 @@ export class SimpleItemSheet extends ItemSheet {
/** @override */ /** @override */
getData() { getData() {
const data = super.getData(); const data = super.getData();
// Handle attribute groups.
EntitySheetHelper.getAttributeData(data); EntitySheetHelper.getAttributeData(data);
return data; return data;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */
async _onSubmit(event, {updateData=null, preventClose=false, preventRender=false}={}) {
let attr = EntitySheetHelper.onSubmit(event);
// Submit the form if attr is true or an attr key.
if ( attr ) {
await super._onSubmit(event, {updateData: updateData, preventClose: preventClose, preventRender: preventRender});
// If attr is a key and not just true, set a very short timeout and retrigger focus after the original element is deleted and the new one is inserted.
if ( attr !== true) {
setTimeout(() => {
$(`input[name="${attr}"]`).parents('.attribute').find('.attribute-value').focus();
}, 10);
}
}
}
/* -------------------------------------------- */
/** @override */
setPosition(options={}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
/** @override */ /** @override */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
// Handle rollable attributes.
html.find(".attributes").on("click", "a.attribute-roll", EntitySheetHelper.onAttributeRoll.bind(this));
// Everything below here is only needed if the sheet is editable // Everything below here is only needed if the sheet is editable
if (!this.options.editable) return; if (!this.options.editable) return;
// Rollable attributes
html.find(".attributes").on("click", "a.attribute-roll", EntitySheetHelper.onAttributeRoll.bind(this));
// Add draggable for macros. // Add draggable for macros.
html.find(".attributes a.attribute-roll").each((i, a) => { html.find(".attributes a.attribute-roll").each((i, a) => {
a.setAttribute("draggable", true); a.setAttribute("draggable", true);

View file

@ -176,110 +176,66 @@ Hooks.on("getItemDirectoryEntryContext", (html, options) => {
}); });
/** async function _onCreateEntity(event) {
* Adds the actor template selection dialog.
*/
ActorDirectory.prototype._onCreateEntity = async (event) => {
// Do not allow the creation event to bubble to other listeners
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
return _simpleDirectoryTemplates(this, event);
_simpleDirectoryTemplates('actor', event);
}
/**
* Adds the item template selection dialog.
*/
ItemDirectory.prototype._onCreateEntity = async (event) => {
// Do not allow the creation event to bubble to other listeners
event.preventDefault();
event.stopPropagation();
_simpleDirectoryTemplates('item', 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. * Display the entity template dialog.
* *
* Helper function to display a dialog if there are multiple template types * Helper function to display a dialog if there are multiple template types defined for the entity type.
* defined for the entity type. * TODO: Refactor in 0.7.x to play more nicely with the Entity.createDialog method
* *1
* @param {string} entityType - 'actor' or 'item' * @param {EntityCollection} entityType - The sidebar tab
* @param {MouseEvent} event - Triggering event * @param {MouseEvent} event - Triggering event
*/ */
async function _simpleDirectoryTemplates(entityType = 'actor', event) { async function _simpleDirectoryTemplates(collection, event) {
// 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. // Retrieve the collection and find any available templates
let entities = entityCollection.filter(a => a.data.flags?.worldbuilding?.isTemplate === true); const entityCollection = collection.tabName === "actors" ? game.actors : game.items;
const cls = collection.tabName === "actors" ? Actor : Item;
// Initialize variables related to the entity class. let templates = entityCollection.filter(a => a.getFlag("worldbuilding", "isTemplate"));
let ent = game.i18n.localize(cls.config.label); let ent = game.i18n.localize(cls.config.label);
// Setup entity data. // Setup default creation data
let type = entityType == 'actor' ? 'character' : 'item'; let type = collection.tabName === "actors" ? 'character' : 'item';
let createData = { let createData = {
name: `${game.i18n.localize("SIMPLE.New")} ${ent}`, name: `${game.i18n.localize("SIMPLE.New")} ${ent}`,
type: type, type: type,
folder: event.currentTarget.dataset.folder folder: event.currentTarget.dataset.folder
}; };
if ( !templates.length ) return cls.create(createData, {renderSheet: true});
// If there's more than one entity template type, create a form. // Build an array of types for the form, including an empty default.
if (entities.length > 0) { let types = [{
// Build an array of types for the form, including an empty default. value: null,
let types = [{ label: game.i18n.localize("SIMPLE.NoTemplate")
value: null, }].concat(templates.map(a => { return { value: a.id, label: a.name } }));
label: game.i18n.localize("SIMPLE.NoTemplate")
}];
// Append each of the user-defined actor/item types. // Render the confirmation dialog window
types = types.concat(entities.map(a => { const templateData = {upper: ent, lower: ent.toLowerCase(), types: types};
return { const dlg = await renderTemplate(`systems/worldbuilding/templates/sidebar/entity-create.html`, templateData);
value: a.data.name, return Dialog.confirm({
label: a.data.name 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});
// Render the entity creation form },
let templateData = {upper: ent, lower: ent.toLowerCase(), types: types}, no: () => {},
dlg = await renderTemplate(`systems/worldbuilding/templates/sidebar/entity-create.html`, templateData); defaultYes: false
});
// Render the confirmation dialog window
Dialog.confirm({
title: `${game.i18n.localize("SIMPLE.Create")} ${createData.name}`,
content: dlg,
yes: html => {
// Get the form data.
const form = html[0].querySelector("form");
const fd = new FormDataExtended(form);
mergeObject(createData, fd.toObject());
// 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});
},
no: () => {},
defaultYes: false
});
}
// Otherwise, just create a blank entity.
else {
cls.create(createData, {renderSheet: true});
}
} }

View file

@ -8,8 +8,11 @@
padding: 5px; padding: 5px;
overflow-y: hidden; overflow-y: hidden;
} }
.worldbuilding form {
height: 100%;
}
.worldbuilding .sheet-header { .worldbuilding .sheet-header {
height: 100px; flex: 0 0 100px;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -49,7 +52,7 @@
height: 28px; height: 28px;
} }
.worldbuilding .tabs { .worldbuilding .tabs {
height: 40px; flex: 0 0 40px;
border-top: 1px solid #AAA; border-top: 1px solid #AAA;
border-bottom: 1px solid #AAA; border-bottom: 1px solid #AAA;
} }

View file

@ -5,8 +5,12 @@
overflow-y: hidden; overflow-y: hidden;
} }
form {
height: 100%;
}
.sheet-header { .sheet-header {
height: 100px; flex: 0 0 100px;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -52,7 +56,7 @@
/* Sheet Tabs */ /* Sheet Tabs */
.tabs { .tabs {
height: 40px; flex: 0 0 40px;
border-top: 1px solid #AAA; border-top: 1px solid #AAA;
border-bottom: 1px solid #AAA; border-bottom: 1px solid #AAA;

View file

@ -1,9 +1,9 @@
{ {
"name": "worldbuilding", "name": "worldbuilding",
"title": "Simple World-Building", "title": "Simple World-Building",
"description": "A minimalist game system with very simple Actor and Item models to support free-form system agnostic gameplay.", "description": "A minimalist game system which provides configurable Actor and Item templates to support free-form system agnostic game-play.",
"version": 0.36, "version": 0.40,
"minimumCoreVersion": "0.7.3", "minimumCoreVersion": "0.6.6",
"compatibleCoreVersion": "0.7.3", "compatibleCoreVersion": "0.7.3",
"templateVersion": 2, "templateVersion": 2,
"author": "Atropos", "author": "Atropos",
@ -23,6 +23,6 @@
"secondaryTokenAttribute": "power", "secondaryTokenAttribute": "power",
"url": "https://gitlab.com/foundrynet/worldbuilding/", "url": "https://gitlab.com/foundrynet/worldbuilding/",
"manifest": "https://gitlab.com/foundrynet/worldbuilding/raw/master/system.json", "manifest": "https://gitlab.com/foundrynet/worldbuilding/raw/master/system.json",
"download": "https://gitlab.com/foundrynet/worldbuilding/-/archive/release-036/worldbuilding-release-036.zip", "download": "https://gitlab.com/foundrynet/worldbuilding/-/archive/release-040/worldbuilding-release-040.zip",
"license": "LICENSE.txt" "license": "LICENSE.txt"
} }

View file

@ -1,4 +1,4 @@
<form class="{{cssClass}}" autocomplete="off"> <form class="flexcol {{cssClass}}" autocomplete="off">
{{!-- Sheet Header --}} {{!-- Sheet Header --}}
<header class="sheet-header"> <header class="sheet-header">

View file

@ -1,4 +1,4 @@
<form class="{{cssClass}}" autocomplete="off"> <form class="flexcol {{cssClass}}" autocomplete="off">
<header class="sheet-header"> <header class="sheet-header">
<img class="profile-img" src="{{item.img}}" data-edit="img" title="{{item.name}}" /> <img class="profile-img" src="{{item.img}}" data-edit="img" title="{{item.name}}" />
<div class="header-fields"> <div class="header-fields">