diff --git a/langs/en-ca.json b/langs/en-ca.json index d103b27..b6d6830 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -71,6 +71,10 @@ "openEmbeddedOnCreate": { "name": "Edit Custom Items Immediately When Created", "description": "Tells the character sheets that have \"Add\" buttons to open the Item's sheet when you create the new item so that you can immediately edit it without needing to click more buttons." + }, + "aspectLimit": { + "name": "Character Aspect Limit", + "description": "Limit how many Aspects a single character can have. The Server is able to bypass this restriction." } }, "sheet-names": { @@ -229,6 +233,9 @@ "item-not-found": "Failed to find an item to delete", "spell-create-failed": "Failed to create a custom spell", "aspect-limit-reached": "You cannot have more than {limit} aspects" + }, + "warn": { + "negative-aspect-limit": "The Aspect limit must be 0 or greater" } }, "dialogs": { diff --git a/module/documents/Actor/Handler.mjs b/module/documents/Actor/Handler.mjs index b93352d..2ef30bb 100644 --- a/module/documents/Actor/Handler.mjs +++ b/module/documents/Actor/Handler.mjs @@ -1,7 +1,7 @@ import PlayerActor from "./Player.mjs"; export class ActorHandler extends Actor { - actorTypes = { + proxyTargets = { player: PlayerActor, }; @@ -11,7 +11,12 @@ export class ActorHandler extends Actor { /** @type {class|undefined} */ get fn() { - return this.actorTypes[this.type]; + return this.proxyTargets[this.type]; + }; + + async proxyFunction(funcName, ...args) { + if (!this.fn?.[funcName]) return; + return await this.fn?.[funcName].bind(this)(...args); }; async openEmbeddedSheet($event) { diff --git a/module/documents/Actor/Player.mjs b/module/documents/Actor/Player.mjs index 3cce41d..a0abd3f 100644 --- a/module/documents/Actor/Player.mjs +++ b/module/documents/Actor/Player.mjs @@ -70,17 +70,23 @@ async function createCustomSpell() { }]); }; +/** @this {Actor} */ +async function atAspectLimit() { + let limit = game.settings.get(`dotdungeon`, `aspectLimit`); + console.log(this.itemTypes.aspect.length, `>=`, limit, `-->`, this.itemTypes.aspect.length >= limit) + return this.itemTypes.aspect.length >= limit; +}; + /** * @param {ItemHandler} item * @this {Actor} */ async function preAspectEmbed(item) { - let limit = 1 - if (this.itemTypes.aspect.length >= limit) { + if (await atAspectLimit.bind(this)()) { ui.notifications.error( game.i18n.format( `dotdungeon.notification.error.aspect-limit-reached`, - { limit } + { limit: game.settings.get(`dotdungeon`, `aspectLimit`) } ), { console: false } ); @@ -89,10 +95,11 @@ async function preAspectEmbed(item) { }; export default { - genericEmbeddedDelete, - genericEmbeddedUpdate, + atAspectLimit, createCustomItem, createCustomAspect, createCustomSpell, + genericEmbeddedDelete, + genericEmbeddedUpdate, preAspectEmbed, }; \ No newline at end of file diff --git a/module/documents/Item/Handler.mjs b/module/documents/Item/Handler.mjs index a136641..4250471 100644 --- a/module/documents/Item/Handler.mjs +++ b/module/documents/Item/Handler.mjs @@ -6,7 +6,7 @@ import AspectItem from "./Aspect.mjs"; export class ItemHandler extends Item { /** @override */ - itemTypes = { + proxyTargets = { aspect: AspectItem, }; @@ -16,7 +16,12 @@ export class ItemHandler extends Item { /** @type {class|undefined} */ get fn() { - return this.itemTypes[this.type]; + return this.proxyTargets[this.type]; + }; + + async proxyFunction(funcName, ...args) { + if (!this.fn?.[funcName]) return; + return await this.fn?.[funcName].bind(this)(...args); }; async _preCreate(...args) { diff --git a/module/settings/world_settings.mjs b/module/settings/world_settings.mjs index b123c27..314ae3b 100644 --- a/module/settings/world_settings.mjs +++ b/module/settings/world_settings.mjs @@ -22,4 +22,41 @@ export default function() { default: "supplies", requiresReload: false, }); + + /* + These two settings are used in coordination in order to prevent the Aspect + Limit from being set to a value that just absolutely stupid (i.e. negative + values, non-integer values). The preSaveAspectLimit is the one that's actually + displayed in the settings dialogue, while the aspectLimit is the one that's + used by all of the code. + */ + game.settings.register(`dotdungeon`, `aspectLimit`, { + scope: `world`, + default: 1, + type: Number, + }); + game.settings.register(`dotdungeon`, `preSaveAspectLimit`, { + name: `dotdungeon.settings.aspectLimit.name`, + hint: `dotdungeon.settings.aspectLimit.description`, + scope: `world`, + config: true, + default: 1, + type: Number, + onChange(value) { + const current = game.settings.get(`dotdungeon`, `aspectLimit`); + if (current == value) return; + + if (value < 0) { + ui.notifications.warn( + `dotdungeon.notification.warn.negative-aspect-limit`, + { localize: true, console: false } + ); + game.settings.set(`dotdungeon`, `preSaveAspectLimit`, current); + return; + }; + let floored = Math.floor(value); + game.settings.set(`dotdungeon`, `aspectLimit`, floored); + game.settings.set(`dotdungeon`, `preSaveAspectLimit`, floored); + }, + }); }; \ No newline at end of file diff --git a/module/sheets/PlayerSheet.mjs b/module/sheets/PlayerSheet.mjs index 31aed66..bf15fba 100644 --- a/module/sheets/PlayerSheet.mjs +++ b/module/sheets/PlayerSheet.mjs @@ -1,6 +1,10 @@ +import { ActorHandler } from "../documents/Actor/Handler.mjs"; import { GenericActorSheet } from "./GenericActorSheet.mjs"; export class PlayerSheet extends GenericActorSheet { + + /** @override {ActorHandler} actor */ + static get defaultOptions() { let opts = mergeObject( super.defaultOptions, @@ -22,7 +26,8 @@ export class PlayerSheet extends GenericActorSheet { async getData() { const ctx = await super.getData(); - const actor = this.actor.toObject(false); + /** @type {ActorHandler} */ + const actor = this.actor; ctx.system = actor.system; ctx.flags = actor.flags; @@ -30,7 +35,7 @@ export class PlayerSheet extends GenericActorSheet { ctx.computed = { canChangeGroup: ctx.settings.playersCanChangeGroup || ctx.isGM, - canAddAspect: ctx.items.aspect.length == 0, + canAddAspect: !await actor.proxyFunction.bind(actor)(`atAspectLimit`), }; console.log(actor.uuid, `context:`, ctx)