diff --git a/README.md b/README.md index 0a9ac76..f530123 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,25 @@ # Text-Based Actors This is an intentionally bare-bones system that features a text-only character sheet, allowing the playing of games that may not otherwise have a Foundry system -implementation. \ No newline at end of file +implementation. + +## Features +There are not too many features included in this system for things like automation +as it's meant to be used to mostly play rules-light games. However there are some +special features that can be enabled on a per-world basis using a world script +to enable feature flags that you want. + +### List of Feature Flags +| Flag | Description +| - | - +| `ROLL_MODE_CONTENT` | Allows players, GMs, and macros to send "blind" chat messages where only the GM gets to see the content. +| `STORABLE_SHEET_SIZE` | Makes it so that certain sheets are able to have their size saved, so that it resumes that size when opened. + +### Example Feature Flag +In order to change these flags, you must make a world script that has something +like the below code in it: + +```js +// v- this is the name of the flag from the table above +taf.FEATURES.STORABLE_SHEET_SIZE = true; +``` \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index f412e22..544b98e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -31,6 +31,7 @@ export default [ ActiveEffect: `readonly`, Dialog: `readonly`, renderTemplate: `readonly`, + TextEditor: `readonly`, }, }, }, @@ -42,6 +43,7 @@ export default [ languageOptions: { globals: { Logger: `readonly`, + taf: `readonly`, }, }, rules: { @@ -49,6 +51,7 @@ export default [ "func-names": [`warn`, `as-needed`], "grouped-accessor-pairs": `error`, "no-alert": `error`, + "no-empty": [`error`, { allowEmptyCatch: true }], "no-implied-eval": `error`, "no-invalid-this": `error`, "no-lonely-if": `error`, diff --git a/src/consts.mjs b/src/consts.mjs index 0c13482..e69de29 100644 --- a/src/consts.mjs +++ b/src/consts.mjs @@ -1,3 +0,0 @@ -export const FEATURE_FLAGS = Object.freeze({ - ROLLMODECONTENT: `Roll Mode Message Content`, -}); diff --git a/src/hooks/renderChatMessage.mjs b/src/hooks/renderChatMessage.mjs index 15f70a0..496a135 100644 --- a/src/hooks/renderChatMessage.mjs +++ b/src/hooks/renderChatMessage.mjs @@ -1,5 +1,3 @@ -import { FEATURE_FLAGS } from "../consts.mjs"; - Hooks.on(`renderChatMessage`, (msg, html) => { // Short-Circuit when the flag isn't set for the message @@ -7,13 +5,12 @@ Hooks.on(`renderChatMessage`, (msg, html) => { return; } - const featureFlags = game.settings.get(game.system.id, `flags`); - const featureFlagEnabled = featureFlags.includes(FEATURE_FLAGS.ROLLMODECONTENT); + const featureFlagEnabled = taf.FEATURES.ROLL_MODE_CONTENT; const contentElement = html.find(`.message-content`)[0]; let content = contentElement.innerHTML; if (featureFlagEnabled && msg.blind && !game.user.isGM) { - content = content.replace(/-=.*?=-/gm, `???`); + content = content.replace(/-=.*?=-/gm, `??`); } else { content = content.replace(/-=|=-/gm, ``); } diff --git a/src/main.mjs b/src/main.mjs index 7f4ae1b..0314ffe 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -44,7 +44,7 @@ Hooks.once(`init`, () => { // MARK: ready hook -Hooks.once( `ready`, () => { +Hooks.once(`ready`, () => { Logger.info(`Ready`); let defaultTab = game.settings.get(game.system.id, `defaultTab`); @@ -56,4 +56,9 @@ Hooks.once( `ready`, () => { ui.sidebar.tabs[defaultTab].activate(); }; }; + + if (game.settings.get(game.system.id, `devMode`)) { + console.log(`%cFeature Flags:`, `color: #00aa00; font-style: bold; font-size: 1.5rem;`); + Logger.table(taf.FEATURES); + }; }); diff --git a/src/settings/dev_settings.mjs b/src/settings/dev_settings.mjs index 229775c..80235b1 100644 --- a/src/settings/dev_settings.mjs +++ b/src/settings/dev_settings.mjs @@ -1,16 +1,25 @@ export function registerDevSettings() { + const isLocalhost = window.location.hostname === `localhost`; + game.settings.register(game.system.id, `devMode`, { + name: `Dev Mode?`, scope: `client`, type: Boolean, - config: false, + config: isLocalhost, default: false, - requiresReload: true, + requiresReload: false, }); game.settings.register(game.system.id, `defaultTab`, { + name: `Default Sidebar Tab`, scope: `client`, type: String, - config: false, + config: isLocalhost, requiresReload: false, + onChange(value) { + if (!ui.sidebar.tabs[value]) { + ui.notifications.warn(`"${value}" cannot be found in the sidebar tabs, it may not work at reload.`); + } + }, }); }; diff --git a/src/settings/world_settings.mjs b/src/settings/world_settings.mjs index db4d7ed..eb3aad7 100644 --- a/src/settings/world_settings.mjs +++ b/src/settings/world_settings.mjs @@ -1,24 +1,2 @@ -import { FEATURE_FLAGS } from "../consts.mjs"; - export function registerWorldSettings() { - game.settings.register(game.system.id, `flags`, { - name: `Feature Flags`, - hint: `World-based feature flags that are used to enable/disable specific behaviours`, - scope: `world`, - type: new foundry.data.fields.SetField( - new foundry.data.fields.StringField( - { - empty: false, - trim: true, - options: Object.values(FEATURE_FLAGS), - }, - ), - { - required: false, - initial: new Set(), - }, - ), - config: true, - requiresReload: true, - }); }; diff --git a/src/sheets/Player/v1.mjs b/src/sheets/Player/v1.mjs index 162af4d..8e775f1 100644 --- a/src/sheets/Player/v1.mjs +++ b/src/sheets/Player/v1.mjs @@ -1,4 +1,6 @@ -export class PlayerSheetv1 extends ActorSheet { +import { SizeStorable } from "../mixins/SizeStorable.mjs"; + +export class PlayerSheetv1 extends SizeStorable(ActorSheet) { static get defaultOptions() { let opts = foundry.utils.mergeObject( super.defaultOptions, diff --git a/src/sheets/mixins/SizeStorable.mjs b/src/sheets/mixins/SizeStorable.mjs new file mode 100644 index 0000000..38ba887 --- /dev/null +++ b/src/sheets/mixins/SizeStorable.mjs @@ -0,0 +1,125 @@ +import { DialogManager } from "../../utils/DialogManager.mjs"; + +/** + * This mixin allows making a class so that it can store the width/height data + * to the sheet or localhost in order to make using the text sheets a lil nicer. + * + * @param {ActorSheet|ItemSheet} cls The Sheet class to augment + * @returns The augmented class + */ +export function SizeStorable(cls) { + + // Don't augment class when the feature isn't enabled + if (!taf.FEATURES.STORABLE_SHEET_SIZE) { + return cls; + } + + return class SizeStorableClass extends cls { + constructor(doc, opts) { + + /* + Find the saved size of the sheet, it takes the following order of precedence + from highest to lowest: + - Locally saved + - Default values on actor + - Default values from constructor + */ + /** @type {string|undefined} */ + let size = localStorage.getItem(`${game.system.id}.size:${doc.uuid}`); + size ??= doc.getFlag(game.system.id, `size`); + + // Apply the saved value to the options + if (size) { + const [ width, height ] = size.split(`,`); + opts.width = width; + opts.height = height; + }; + + super(doc, opts); + }; + + get hasLocalSize() { + return localStorage.getItem(`${game.system.id}.size:${this.object.uuid}`) != null; + }; + + get hasGlobalSize() { + return this.object.getFlag(game.system.id, `size`) != null; + }; + + _getHeaderButtons() { + return [ + { + class: `size-save`, + icon: `fa-solid fa-floppy-disk`, + label: `Save Size`, + onclick: () => { + + const buttons = { + saveGlobal: { + label: `Save Global Size`, + callback: () => { + this.object.setFlag( + game.system.id, + `size`, + `${this.position.width},${this.position.height}`, + ); + }, + }, + saveLocal: { + label: `Save For Me Only`, + callback: () => { + localStorage.setItem( + `${game.system.id}.size:${this.object.uuid}`, + `${this.position.width},${this.position.height}`, + ); + }, + }, + }; + + // Add resets if there is a size already + if (this.hasGlobalSize) { + buttons.resetGlobal = { + label: `Reset Global Size`, + callback: () => { + this.object.unsetFlag(game.system.id, `size`); + }, + }; + }; + + if (this.hasLocalSize) { + buttons.resetLocal = { + label: `Reset Size For Me Only`, + callback: () => { + localStorage.removeItem(`${game.system.id}.size:${this.object.uuid}`); + }, + }; + }; + + // When a non-GM is using this system, we only want to save local sizes + if (!game.user.isGM) { + delete buttons.saveGlobal; + delete buttons.resetGlobal; + }; + + DialogManager.createOrFocus( + `${this.object.uuid}:size-save`, + { + title: `Save size of sheet: ${this.title}`, + content: `Saving the size of this sheet will cause it to open at the size it is when you press the save button`, + buttons, + render: (html) => { + const el = html[2]; + el.style = `display: grid; grid-template-columns: 1fr 1fr; gap: 8px;`; + }, + }, + { + jQuery: true, + }, + ); + }, + }, + ...super._getHeaderButtons(), + ]; + }; + }; +}; diff --git a/src/utils/feature_flags/rollModeMessageContent.mjs b/src/utils/feature_flags/rollModeMessageContent.mjs index 059852a..67ed613 100644 --- a/src/utils/feature_flags/rollModeMessageContent.mjs +++ b/src/utils/feature_flags/rollModeMessageContent.mjs @@ -1,8 +1,5 @@ -import { FEATURE_FLAGS } from "../../consts.mjs"; - export function hideMessageText(content) { - const featureFlags = game.settings.get(game.system.id, `flags`); - const hideContent = featureFlags.includes(FEATURE_FLAGS.ROLLMODECONTENT); + const hideContent = taf.FEATURES.ROLL_MODE_CONTENT; if (hideContent) { return `-=${content}=-`; } diff --git a/src/utils/globalTaf.mjs b/src/utils/globalTaf.mjs index 3ed03fe..899328e 100644 --- a/src/utils/globalTaf.mjs +++ b/src/utils/globalTaf.mjs @@ -1,11 +1,18 @@ -import { FEATURE_FLAGS } from "../consts.mjs"; import { hideMessageText } from "./feature_flags/rollModeMessageContent.mjs"; -globalThis.taf = Object.freeze({ - utils: { - hideMessageText, +Object.defineProperty( + globalThis, + `taf`, + { + value: Object.freeze({ + utils: Object.freeze({ + hideMessageText, + }), + FEATURES: Object.preventExtensions({ + ROLL_MODE_CONTENT: false, + STORABLE_SHEET_SIZE: false, + }), + }), + writable: false, }, - const: { - FEATURE_FLAGS, - }, -}); +); diff --git a/system.json b/system.json index 712924d..d1561a6 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "taf", "title": "Text-Based Actors", "description": "", - "version": "1.2.0", + "version": "2.0.0", "download": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/dotdungeon.zip", "manifest": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/system.json", "url": "https://github.com/Oliver-Akins/Text-Actors-Foundry",