Add saving and editing of default attributes and their values (closes #29)
This commit is contained in:
parent
5159db3d33
commit
d540cc72f6
6 changed files with 178 additions and 34 deletions
|
|
@ -30,6 +30,11 @@
|
||||||
"false": "Not Resizable",
|
"false": "Not Resizable",
|
||||||
"true": "Resizable"
|
"true": "Resizable"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"actorDefaultAttributes": {
|
||||||
|
"name": "Remove Default Attributes",
|
||||||
|
"hint": "This removes the default attributes that are applied when a new actor is created, making it so that no attributes get created alongside the actor.",
|
||||||
|
"label": "Remove Attributes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sheet-names": {
|
"sheet-names": {
|
||||||
|
|
@ -59,7 +64,8 @@
|
||||||
"has-max": "Has Maximum?",
|
"has-max": "Has Maximum?",
|
||||||
"name-placeholder": "Attribute Name...",
|
"name-placeholder": "Attribute Name...",
|
||||||
"no-attributes": "No attributes yet",
|
"no-attributes": "No attributes yet",
|
||||||
"add-new-attribute": "Add New Attribute"
|
"add-new-attribute": "Add New Attribute",
|
||||||
|
"default-attribute-values": "Default Attribute Values"
|
||||||
},
|
},
|
||||||
"PlayerSheet": {
|
"PlayerSheet": {
|
||||||
"manage-attributes": "Manage Attributes",
|
"manage-attributes": "Manage Attributes",
|
||||||
|
|
@ -100,6 +106,9 @@
|
||||||
"invalid-socket": "Invalid socket data received, this means a module or system bug is present.",
|
"invalid-socket": "Invalid socket data received, this means a module or system bug is present.",
|
||||||
"unknown-socket-event": "An unknown socket event was received: {event}",
|
"unknown-socket-event": "An unknown socket event was received: {event}",
|
||||||
"malformed-socket-payload": "Socket event \"{event}\" received with malformed payload. Details: {details}"
|
"malformed-socket-payload": "Socket event \"{event}\" received with malformed payload. Details: {details}"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"saved-default-attributes": "Successfully saved default Actor attributes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { __ID__, filePath } from "../consts.mjs";
|
import { __ID__, filePath } from "../consts.mjs";
|
||||||
import { attributeSorter } from "../utils/attributeSort.mjs";
|
import { attributeSorter } from "../utils/attributeSort.mjs";
|
||||||
|
import { ask } from "../utils/DialogManager.mjs";
|
||||||
import { localizer } from "../utils/localizer.mjs";
|
import { localizer } from "../utils/localizer.mjs";
|
||||||
import { toID } from "../utils/toID.mjs";
|
import { toID } from "../utils/toID.mjs";
|
||||||
|
|
||||||
|
|
@ -22,6 +23,14 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
controls: [
|
||||||
|
{
|
||||||
|
icon: `fa-solid fa-globe`,
|
||||||
|
label: `Save As Defaults`,
|
||||||
|
visible: () => game.user.isGM,
|
||||||
|
action: `saveAsDefault`,
|
||||||
|
}
|
||||||
|
],
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
submitOnChange: false,
|
submitOnChange: false,
|
||||||
|
|
@ -31,6 +40,7 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
actions: {
|
actions: {
|
||||||
addNew: this.#addNew,
|
addNew: this.#addNew,
|
||||||
removeAttribute: this.#remove,
|
removeAttribute: this.#remove,
|
||||||
|
saveAsDefault: this.#saveAsDefaults,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -73,16 +83,39 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
new DragDrop.implementation({
|
new DragDrop.implementation({
|
||||||
dragSelector: `.attribute-drag-handle`,
|
dragSelector: `.attribute-drag-handle`,
|
||||||
dropSelector: `.attributes`,
|
dropSelector: `.attributes`,
|
||||||
permissions: {
|
|
||||||
dragstart: this._canDragStart.bind(this),
|
|
||||||
drop: this._canDragDrop.bind(this),
|
|
||||||
},
|
|
||||||
callbacks: {
|
callbacks: {
|
||||||
dragstart: this._onDragStart.bind(this),
|
dragstart: this._onDragStart.bind(this),
|
||||||
drop: this._onDrop.bind(this),
|
drop: this._onDrop.bind(this),
|
||||||
},
|
},
|
||||||
}).bind(this.element);
|
}).bind(this.element);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @this {AttributeManager} */
|
||||||
|
static async #onSubmit() {
|
||||||
|
const entries = Object.entries(this.#attributes)
|
||||||
|
.map(([id, attr]) => {
|
||||||
|
if (attr == null) {
|
||||||
|
return [ id, attr ];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (attr.isNew) {
|
||||||
|
delete attr.isNew;
|
||||||
|
return [ toID(attr.name), attr ];
|
||||||
|
};
|
||||||
|
|
||||||
|
return [ id, attr ];
|
||||||
|
});
|
||||||
|
const data = Object.fromEntries(entries);
|
||||||
|
this.#attributes = data;
|
||||||
|
|
||||||
|
const diff = diffObject(
|
||||||
|
this.#doc.system.attr,
|
||||||
|
data,
|
||||||
|
{ inner: false, deletionKeys: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.#doc.update({ "system.attr": diff });
|
||||||
|
};
|
||||||
// #endregion Lifecycle
|
// #endregion Lifecycle
|
||||||
|
|
||||||
// #region Data Prep
|
// #region Data Prep
|
||||||
|
|
@ -152,48 +185,87 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
static async #remove($e, element) {
|
static async #remove($e, element) {
|
||||||
const attribute = element.closest(`[data-attribute]`)?.dataset.attribute;
|
const attribute = element.closest(`[data-attribute]`)?.dataset.attribute;
|
||||||
if (!attribute) { return };
|
if (!attribute) { return };
|
||||||
|
if (game.release.generation < 14) {
|
||||||
delete this.#attributes[attribute];
|
delete this.#attributes[attribute];
|
||||||
this.#attributes[`-=${attribute}`] = null;
|
this.#attributes[`-=${attribute}`] = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.#attributes[attribute] = _del;
|
||||||
|
}
|
||||||
await this.render({ parts: [ `attributes` ] });
|
await this.render({ parts: [ `attributes` ] });
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @this {AttributeManager} */
|
/** @this {AttributeManager} */
|
||||||
static async #onSubmit() {
|
static async #saveAsDefaults() {
|
||||||
const entries = Object.entries(this.#attributes)
|
const attrs = deepClone(this.#attributes);
|
||||||
.map(([id, attr]) => {
|
|
||||||
if (attr == null) {
|
|
||||||
return [ id, attr ];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (attr.isNew) {
|
// Prompt the user for what values they want to save the attributes with
|
||||||
delete attr.isNew;
|
const inputs = [];
|
||||||
return [ toID(attr.name), attr ];
|
for (const attr of Object.values(attrs)) {
|
||||||
};
|
const id = toID(attr.name);
|
||||||
|
|
||||||
return [ id, attr ];
|
if (attr.isRange) {
|
||||||
});
|
inputs.push(
|
||||||
const data = Object.fromEntries(entries);
|
{
|
||||||
this.#attributes = data;
|
type: `collapse`,
|
||||||
|
summary: attr.name,
|
||||||
const diff = diffObject(
|
inputs: [
|
||||||
this.#doc.system.attr,
|
{
|
||||||
data,
|
key: `${id}.value`,
|
||||||
{ inner: false, deletionKeys: true },
|
type: `input`,
|
||||||
|
inputType: `number`,
|
||||||
|
label: `Value`,
|
||||||
|
defaultValue: attr.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `${id}.max`,
|
||||||
|
type: `input`,
|
||||||
|
inputType: `number`,
|
||||||
|
label: `Maximum`,
|
||||||
|
defaultValue: attr.max,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: `divider` }
|
||||||
);
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
await this.#doc.update({ "system.attr": diff });
|
inputs.push({
|
||||||
|
key: `${id}.value`,
|
||||||
|
type: `input`,
|
||||||
|
inputType: `number`,
|
||||||
|
label: `${attr.name}`,
|
||||||
|
defaultValue: attr.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
inputs.push({ type: `divider` });
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = {
|
||||||
|
id: `${this.#doc.id}-global-attr-saving`,
|
||||||
|
inputs: inputs.slice(0, -1),
|
||||||
|
alwaysUseAnswerObject: true,
|
||||||
|
window: { title: `taf.Apps.AttributeManager.default-attribute-values` },
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await ask(prompt);
|
||||||
|
switch (response.state) {
|
||||||
|
case `errored`:
|
||||||
|
ui.notifications.error(response.error);
|
||||||
|
case `fronted`:
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!response.answers) { return };
|
||||||
|
|
||||||
|
const fullAttrs = mergeObject(attrs, response.answers);
|
||||||
|
game.settings.set(__ID__, `actorDefaultAttributes`, fullAttrs);
|
||||||
|
ui.notifications.success(`taf.notifs.success.saved-default-attributes`);
|
||||||
};
|
};
|
||||||
// #endregion Actions
|
// #endregion Actions
|
||||||
|
|
||||||
// #region Drag & Drop
|
// #region Drag & Drop
|
||||||
_canDragStart() {
|
|
||||||
return this.#doc.isOwner;
|
|
||||||
};
|
|
||||||
|
|
||||||
_canDragDrop() {
|
|
||||||
return this.#doc.isOwner;
|
|
||||||
};
|
|
||||||
|
|
||||||
_onDragStart(event) {
|
_onDragStart(event) {
|
||||||
const target = event.currentTarget.closest(`[data-attribute]`);
|
const target = event.currentTarget.closest(`[data-attribute]`);
|
||||||
if (`link` in event.target.dataset) { return };
|
if (`link` in event.target.dataset) { return };
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,24 @@
|
||||||
|
import { __ID__ } from "../consts.mjs";
|
||||||
|
|
||||||
const { Actor } = foundry.documents;
|
const { Actor } = foundry.documents;
|
||||||
|
const { hasProperty } = foundry.utils;
|
||||||
|
|
||||||
export class TAFActor extends Actor {
|
export class TAFActor extends Actor {
|
||||||
|
|
||||||
|
// #region Lifecycle
|
||||||
|
async _preCreate(data, options, user) {
|
||||||
|
|
||||||
|
// Assign the defaults from the world setting if they exist
|
||||||
|
const defaults = game.settings.get(__ID__, `actorDefaultAttributes`) ?? {};
|
||||||
|
if (!hasProperty(data, `system.attr`)) {
|
||||||
|
const value = game.release.generation > 13 ? _replace(defaults) : defaults;
|
||||||
|
this.updateSource({ "system.==attr": value });
|
||||||
|
};
|
||||||
|
|
||||||
|
return super._preCreate(data, options, user);
|
||||||
|
};
|
||||||
|
// #endregion Lifecycle
|
||||||
|
|
||||||
async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) {
|
async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) {
|
||||||
const attr = foundry.utils.getProperty(this.system, attribute);
|
const attr = foundry.utils.getProperty(this.system, attribute);
|
||||||
const current = isBar ? attr.value : attr;
|
const current = isBar ? attr.value : attr;
|
||||||
|
|
|
||||||
38
module/hooks/renderSettingsConfig.mjs
Normal file
38
module/hooks/renderSettingsConfig.mjs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { __ID__ } from "../consts.mjs";
|
||||||
|
|
||||||
|
Hooks.on(`renderSettingsConfig`, (app, html, context, options) => {
|
||||||
|
/*
|
||||||
|
This section is used to insert a button into the settings config that unsets
|
||||||
|
a world setting when it exists but doesn't allow any other form of editing it.
|
||||||
|
*/
|
||||||
|
if (game.user.isGM && game.settings.get(__ID__, `actorDefaultAttributes`)) {
|
||||||
|
const formGroup = document.createElement(`div`);
|
||||||
|
formGroup.classList = `form-group`;
|
||||||
|
|
||||||
|
const label = document.createElement(`div`);
|
||||||
|
label.innerHTML = _loc(`taf.settings.actorDefaultAttributes.name`);
|
||||||
|
|
||||||
|
const formFields = document.createElement(`div`);
|
||||||
|
formFields.classList = `form-fields`;
|
||||||
|
|
||||||
|
const button = document.createElement(`button`);
|
||||||
|
button.type = `button`;
|
||||||
|
button.innerHTML = _loc(`taf.settings.actorDefaultAttributes.label`);
|
||||||
|
button.addEventListener(`click`, () => {
|
||||||
|
game.settings.set(__ID__, `actorDefaultAttributes`, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hint = document.createElement(`p`);
|
||||||
|
hint.classList = `hint`;
|
||||||
|
hint.innerHTML = _loc(`taf.settings.actorDefaultAttributes.hint`);
|
||||||
|
|
||||||
|
formFields.appendChild(button);
|
||||||
|
formGroup.appendChild(label);
|
||||||
|
formGroup.appendChild(formFields);
|
||||||
|
formGroup.appendChild(hint);
|
||||||
|
|
||||||
|
/** @type {HTMLElement|undefined} */
|
||||||
|
const tab = html.querySelector(`.tab[data-group="categories"][data-tab="system"]`);
|
||||||
|
tab?.insertAdjacentElement(`afterbegin`, formGroup);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
import "./api.mjs";
|
import "./api.mjs";
|
||||||
import "./hooks/init.mjs";
|
import "./hooks/init.mjs";
|
||||||
import "./hooks/userConnected.mjs";
|
import "./hooks/userConnected.mjs";
|
||||||
|
import "./hooks/renderSettingsConfig.mjs";
|
||||||
|
|
|
||||||
|
|
@ -59,4 +59,10 @@ export function registerWorldSettings() {
|
||||||
}),
|
}),
|
||||||
scope: `world`,
|
scope: `world`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
game.settings.register(__ID__, `actorDefaultAttributes`, {
|
||||||
|
config: false,
|
||||||
|
type: Object,
|
||||||
|
scope: `world`,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue