From 5b2b29ae4c613c97747cbd6d150a9858508d4baa Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Mon, 18 Apr 2022 03:09:05 +0200 Subject: [PATCH] refactor: replace libWrapper with mixins --- README.md | 8 -- src/hooks/init.js | 25 +----- src/mixins/index.js | 5 ++ src/mixins/token.js | 124 ++++++++++++++++++++++++++++ src/shims/libWrapperShim.js | 156 ------------------------------------ src/wrappers/token.js | 109 ------------------------- 6 files changed, 131 insertions(+), 296 deletions(-) create mode 100644 src/mixins/index.js create mode 100644 src/mixins/token.js delete mode 100644 src/shims/libWrapperShim.js delete mode 100644 src/wrappers/token.js diff --git a/README.md b/README.md index 8ce5e60..46370b6 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,6 @@ the Setup menu of the application. https://git.f3l.de/ghost/darkness-dependent-vision/-/raw/latest/src/module.json?inline=false -### libWrapper - -This module uses the [libWrapper] library for wrapping core methods. It is only -a soft dependency (a shim is provided) but it is highly recommended to install -libWrapper as a module for the best experience and compatibility with other -modules. - ## Usage In order to configure the darkness range in which a token has dim and bright @@ -116,7 +109,6 @@ specified either inside the source file or by an accompanying `.license` file, but for some files, the licenses are specified in [.reuse/dep5]. [Foundry Virtual Tabletop]: http://foundryvtt.com -[libWrapper]: https://github.com/ruipin/fvtt-lib-wrapper [LIMITED LICENSE AGREEMENT FOR MODULE DEVELOPMENT]: https://foundryvtt.com/article/license/ [REUSE]: https://reuse.software/ [.reuse/dep5]: .reuse/dep5 diff --git a/src/hooks/init.js b/src/hooks/init.js index 4e4d0e3..90be0c3 100644 --- a/src/hooks/init.js +++ b/src/hooks/init.js @@ -5,9 +5,8 @@ import { packageName } from '../config'; import registerHandlebarsPartials from '../handlebars-partials'; import logger from '../logger'; +import { registerMixins } from '../mixins/index.js'; import registerSettings from '../setiings'; -import { libWrapper } from '../shims/libWrapperShim'; -import { getBrightRadius, getDimRadius, updateVisionSource } from '../wrappers/token'; export default function registerForInitHook() { Hooks.on('init', onInit); @@ -16,27 +15,7 @@ export default function registerForInitHook() { async function onInit() { logger.info(`Initializing ${packageName}`); - const dimRadiusTarget = 'Token.prototype.dimRadius'; - try { - libWrapper.register(packageName, dimRadiusTarget, getDimRadius, 'OVERRIDE'); - } catch (e) { - logger.warn(`Failed to override ${dimRadiusTarget}, some things might not work correctly:`, e); - } - - const brightRadiusTarget = 'Token.prototype.brightRadius'; - try { - libWrapper.register(packageName, brightRadiusTarget, getBrightRadius, 'OVERRIDE'); - } catch (e) { - logger.warn(`Failed to override ${brightRadiusTarget}, some things might not work correctly:`, e); - } - - const updateVisionSourceTarget = 'Token.prototype.updateVisionSource'; - try { - libWrapper.register(packageName, updateVisionSourceTarget, updateVisionSource, 'OVERRIDE'); - } catch (e) { - logger.warn(`Failed to override ${updateVisionSourceTarget}, some things might not work correctly:`, e); - } - + registerMixins(); registerSettings(); await registerHandlebarsPartials(); } diff --git a/src/mixins/index.js b/src/mixins/index.js new file mode 100644 index 0000000..f705138 --- /dev/null +++ b/src/mixins/index.js @@ -0,0 +1,5 @@ +import { registerTokenMixin } from './token'; + +export function registerMixins() { + registerTokenMixin(); +} diff --git a/src/mixins/token.js b/src/mixins/token.js new file mode 100644 index 0000000..ea7fbec --- /dev/null +++ b/src/mixins/token.js @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: 2021 Johannes Loher +// +// SPDX-License-Identifier: MIT + +import { packageName } from '../config'; + +export function registerTokenMixin() { + CONFIG.Token.objectClass = TokenMixin(CONFIG.Token.objectClass); +} + +function TokenMixin(BaseToken) { + return class extends BaseToken { + /** + * Translate the token's sight distance in units into a radius in pixels. + * @return {number} The sight radius in pixels + * @override + */ + get dimRadius() { + const dimSight = this._dimVision; + let r = Math.abs(this.data.dimLight) > Math.abs(dimSight) ? this.data.dimLight : dimSight; + return this.getLightRadius(r); + } + + /** + * Does this {@link Token} have dim vision, considering the darkness level of + * its containing {@link Scene}? + * @private + */ + get _hasDimVision() { + const dimVisionDarknessMin = this.document.getFlag(packageName, 'dimVisionDarknessMin') ?? 0; + const dimVisionDarknessMax = this.document.getFlag(packageName, 'dimVisionDarknessMax') ?? 1; + const darkness = this.document.parent.data.darkness; + return dimVisionDarknessMin <= darkness && darkness <= dimVisionDarknessMax; + } + + /** + * Get this {@link Token}'s dim vision distance of in grid units, considering + * the darkness level of its containing {@link Scene}. + * + * @returns {number} The the number of grid units that this {@link Token} has + * dim vision + * @private + */ + get _dimVision() { + return this._hasDimVision ? this.data.dimSight : 0; + } + + /** + * Translate the token's bright light distance in units into a radius in pixels. + * @return {number} The bright radius in pixels + * @override + */ + get brightRadius() { + const brightSight = this._brightVision; + let r = Math.abs(this.data.brightLight) > Math.abs(brightSight) ? this.data.brightLight : brightSight; + return this.getLightRadius(r); + } + + /** + * Does this {@link Token} have bright vision, considering the darkness level of + * its containing {@link Scene}? + * @override + */ + get _hasBrightVision() { + const brightVisionDarknessMin = this.document.getFlag(packageName, 'brightVisionDarknessMin') ?? 0; + const brightVisionDarknessMax = this.document.getFlag(packageName, 'brightVisionDarknessMax') ?? 1; + const darkness = this.document.parent.data.darkness; + return brightVisionDarknessMin <= darkness && darkness <= brightVisionDarknessMax; + } + + /** + * Get this {@link Token}'s bright vision distance in grid units, considering + * the darkness level of its containing {@link Scene}. + * + * @returns {number} The the number of grid units that this {@link Token} has + * bright vision + * @private + */ + get _brightVision() { + return this._hasBrightVision ? this.data.brightSight : 0; + } + + /** + * Update an Token vision source associated for this token. + * @param {boolean} [defer] Defer refreshing the LightingLayer to manually call that refresh later. + * @param {boolean} [deleted] Indicate that this vision source has been deleted. + * @param {boolean} [skipUpdateFog] Never update the Fog exploration progress for this update. + * @override + */ + updateVisionSource({ defer = false, deleted = false, skipUpdateFog = false } = {}) { + // Prepare data + const origin = this.getSightOrigin(); + const sourceId = this.sourceId; + const d = canvas.dimensions; + const isVisionSource = this._isVisionSource(); + + // Initialize vision source + if (isVisionSource && !deleted) { + const dimSight = this._dimVision; + const brightSight = this._brightVision; + let dim = Math.min(this.getLightRadius(dimSight), d.maxR); + const bright = Math.min(this.getLightRadius(brightSight), d.maxR); + this.vision.initialize({ + x: origin.x, + y: origin.y, + dim: dim, + bright: bright, + angle: this.data.sightAngle, + rotation: this.data.rotation, + }); + canvas.sight.sources.set(sourceId, this.vision); + } + + // Remove vision source + else canvas.sight.sources.delete(sourceId); + + // Schedule a perception update + if (!defer && (isVisionSource || deleted)) + canvas.perception.schedule({ + sight: { refresh: true, skipUpdateFog }, + }); + } + }; +} diff --git a/src/shims/libWrapperShim.js b/src/shims/libWrapperShim.js deleted file mode 100644 index 729f10f..0000000 --- a/src/shims/libWrapperShim.js +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro - -'use strict'; - -// A shim for the libWrapper library -export let libWrapper = undefined; - -export const VERSIONS = [1, 12, 1]; -export const TGT_SPLIT_RE = new RegExp('([^.[]+|\\[(\'([^\'\\\\]|\\\\.)+?\'|"([^"\\\\]|\\\\.)+?")\\])', 'g'); -export const TGT_CLEANUP_RE = new RegExp('(^\\[\'|\'\\]$|^\\["|"\\]$)', 'g'); - -// Main shim code -Hooks.once('init', () => { - // Check if the real module is already loaded - if so, use it - if (globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) { - libWrapper = globalThis.libWrapper; - return; - } - - // Fallback implementation - libWrapper = class { - static get is_fallback() { - return true; - } - - static get WRAPPER() { - return 'WRAPPER'; - } - static get MIXED() { - return 'MIXED'; - } - static get OVERRIDE() { - return 'OVERRIDE'; - } - - static register(package_id, target, fn, type = 'MIXED', { chain = undefined, bind = [] } = {}) { - const is_setter = target.endsWith('#set'); - target = !is_setter ? target : target.slice(0, -4); - const split = target.match(TGT_SPLIT_RE).map((x) => x.replace(/\\(.)/g, '$1').replace(TGT_CLEANUP_RE, '')); - const root_nm = split.splice(0, 1)[0]; - - let obj, fn_name; - if (split.length == 0) { - obj = globalThis; - fn_name = root_nm; - } else { - const _eval = eval; - fn_name = split.pop(); - obj = split.reduce((x, y) => x[y], globalThis[root_nm] ?? _eval(root_nm)); - } - - let iObj = obj; - let descriptor = null; - while (iObj) { - descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name); - if (descriptor) break; - iObj = Object.getPrototypeOf(iObj); - } - if (!descriptor || descriptor?.configurable === false) - throw new Error( - `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`, - ); - - let original = null; - const wrapper = - chain ?? (type.toUpperCase?.() != 'OVERRIDE' && type != 3) - ? function (...args) { - return fn.call(this, original.bind(this), ...bind, ...args); - } - : function (...args) { - return fn.call(this, ...bind, ...args); - }; - if (!is_setter) { - if (descriptor.value) { - original = descriptor.value; - descriptor.value = wrapper; - } else { - original = descriptor.get; - descriptor.get = wrapper; - } - } else { - if (!descriptor.set) throw new Error(`libWrapper Shim: '${target}' does not have a setter`); - original = descriptor.set; - descriptor.set = wrapper; - } - - descriptor.configurable = true; - Object.defineProperty(obj, fn_name, descriptor); - } - }; - - //************** USER CUSTOMIZABLE: - // Set up the ready hook that shows the "libWrapper not installed" warning dialog. Remove if undesired. - { - //************** USER CUSTOMIZABLE: - // Package ID & Package Title - by default attempts to auto-detect, but you might want to hardcode your package ID and title here to avoid potential auto-detect issues - const [PACKAGE_ID, PACKAGE_TITLE] = (() => { - const match = (import.meta?.url ?? Error().stack)?.match(/\/(worlds|systems|modules)\/(.+)(?=\/)/i); - if (match?.length !== 3) return [null, null]; - const dirs = match[2].split('/'); - if (match[1] === 'worlds') - return dirs.find((n) => n && game.world.id === n) ? [game.world.id, game.world.title] : [null, null]; - if (match[1] === 'systems') - return dirs.find((n) => n && game.system.id === n) ? [game.system.id, game.system.data.title] : [null, null]; - const id = dirs.find((n) => n && game.modules.has(n)); - return [id, game.modules.get(id)?.data?.title]; - })(); - - if (!PACKAGE_ID || !PACKAGE_TITLE) { - console.error( - 'libWrapper Shim: Could not auto-detect package ID and/or title. The libWrapper fallback warning dialog will be disabled.', - ); - return; - } - - Hooks.once('ready', () => { - //************** USER CUSTOMIZABLE: - // Title and message for the dialog shown when the real libWrapper is not installed. - const FALLBACK_MESSAGE_TITLE = PACKAGE_TITLE; - const FALLBACK_MESSAGE = ` -

'${PACKAGE_TITLE}' depends on the 'libWrapper' module, which is not present.

-

A fallback implementation will be used, which increases the chance of compatibility issues with other modules.

-

'libWrapper' is a library which provides package developers with a simple way to modify core Foundry VTT code, while reducing the likelihood of conflict with other packages.

-

You can install it from the "Add-on Modules" tab in the Foundry VTT Setup, from the Foundry VTT package repository, or from libWrapper's Github page.

- `; - - // Settings key used for the "Don't remind me again" setting - const DONT_REMIND_AGAIN_KEY = 'libwrapper-dont-remind-again'; - - // Dialog code - console.warn(`${PACKAGE_TITLE}: libWrapper not present, using fallback implementation.`); - game.settings.register(PACKAGE_ID, DONT_REMIND_AGAIN_KEY, { - name: '', - default: false, - type: Boolean, - scope: 'world', - config: false, - }); - if (game.user.isGM && !game.settings.get(PACKAGE_ID, DONT_REMIND_AGAIN_KEY)) { - new Dialog({ - title: FALLBACK_MESSAGE_TITLE, - content: FALLBACK_MESSAGE, - buttons: { - ok: { icon: '', label: 'Understood' }, - dont_remind: { - icon: '', - label: "Don't remind me again", - callback: () => game.settings.set(PACKAGE_ID, DONT_REMIND_AGAIN_KEY, true), - }, - }, - }).render(true); - } - }); - } -}); diff --git a/src/wrappers/token.js b/src/wrappers/token.js deleted file mode 100644 index ed1acb6..0000000 --- a/src/wrappers/token.js +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Johannes Loher -// -// SPDX-License-Identifier: MIT - -import { packageName } from '../config'; - -/** - * Translate the token's sight distance in units into a radius in pixels. - * @return {number} The sight radius in pixels - */ -export function getDimRadius() { - const dimSight = getDimVision.call(this); - let r = Math.abs(this.data.dimLight) > Math.abs(dimSight) ? this.data.dimLight : dimSight; - return this.getLightRadius(r); -} - -/** - * Translate the token's bright light distance in units into a radius in pixels. - * @return {number} The bright radius in pixels - */ -export function getBrightRadius() { - const brightSight = getBrightVision.call(this); - let r = Math.abs(this.data.brightLight) > Math.abs(brightSight) ? this.data.brightLight : brightSight; - return this.getLightRadius(r); -} - -/** - * Update an Token vision source associated for this token. - * @param {boolean} [defer] Defer refreshing the LightingLayer to manually call that refresh later. - * @param {boolean} [deleted] Indicate that this vision source has been deleted. - * @param {boolean} [skipUpdateFog] Never update the Fog exploration progress for this update. - */ -export function updateVisionSource({ defer = false, deleted = false, skipUpdateFog = false } = {}) { - // Prepare data - const origin = this.getSightOrigin(); - const sourceId = this.sourceId; - const d = canvas.dimensions; - const isVisionSource = this._isVisionSource(); - - // Initialize vision source - if (isVisionSource && !deleted) { - const dimSight = getDimVision.call(this); - const brightSight = getBrightVision.call(this); - let dim = Math.min(this.getLightRadius(dimSight), d.maxR); - const bright = Math.min(this.getLightRadius(brightSight), d.maxR); - this.vision.initialize({ - x: origin.x, - y: origin.y, - dim: dim, - bright: bright, - angle: this.data.sightAngle, - rotation: this.data.rotation, - }); - canvas.sight.sources.set(sourceId, this.vision); - } - - // Remove vision source - else canvas.sight.sources.delete(sourceId); - - // Schedule a perception update - if (!defer && (isVisionSource || deleted)) - canvas.perception.schedule({ - sight: { refresh: true, skipUpdateFog }, - }); -} - -/** - * Does this {@link Token} have dim vision, considering the darkness level of - * its containing {@link Scene}? - */ -function hasDimVision() { - const dimVisionDarknessMin = this.document.getFlag(packageName, 'dimVisionDarknessMin') ?? 0; - const dimVisionDarknessMax = this.document.getFlag(packageName, 'dimVisionDarknessMax') ?? 1; - const darkness = this.document.parent.data.darkness; - return dimVisionDarknessMin <= darkness && darkness <= dimVisionDarknessMax; -} - -/** - * Does this {@link Token} have bright vision, considering the darkness level of - * its containing {@link Scene}? - */ -function hasBrightVision() { - const brightVisionDarknessMin = this.document.getFlag(packageName, 'brightVisionDarknessMin') ?? 0; - const brightVisionDarknessMax = this.document.getFlag(packageName, 'brightVisionDarknessMax') ?? 1; - const darkness = this.document.parent.data.darkness; - return brightVisionDarknessMin <= darkness && darkness <= brightVisionDarknessMax; -} - -/** - * Get this {@link Token}'s dim vision distance of in grid units, considering - * the darkness level of its containing {@link Scene}. - * - * @returns {number} The the number of grid units that this {@link Token} has - * dim vision - */ -function getDimVision() { - return hasDimVision.call(this) ? this.data.dimSight : 0; -} - -/** - * Get this {@link Token}'s bright vision distance in grid units, considering - * the darkness level of its containing {@link Scene}. - * - * @returns {number} The the number of grid units that this {@link Token} has - * bright vision - */ -function getBrightVision() { - return hasBrightVision.call(this) ? this.data.brightSight : 0; -}