From 1215ddf55ad713bb5e29e0a37586b782a43a3865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20V=C3=B6gele?= Date: Wed, 5 May 2021 01:06:33 +0200 Subject: [PATCH] Use libWrapper for function hooks (resolves #5) --- CHANGELOG.md | 4 ++ lib/libwrapper_shim.js | 61 +++++++++++++++++++++++++ module.json | 1 + src/features/door_control_icon_scale.js | 5 +- src/main.js | 31 ++++++------- 5 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 lib/libwrapper_shim.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b43eb6..74e6e04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## In development +### Compatibility +- Smart Doors now uses the libwrapper module and as a result is now compatible with the module "FoundryVTT Arms Reach" + ## 1.2.5 ### New features - Synchronized doors can now be configured to synchronize their secret door status as well diff --git a/lib/libwrapper_shim.js b/lib/libwrapper_shim.js new file mode 100644 index 0000000..dc76133 --- /dev/null +++ b/lib/libwrapper_shim.js @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro + +'use strict'; + +// A shim for the libWrapper library +export let libWrapper = undefined; + +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 register(module, target, fn, type="MIXED", {chain=undefined}={}) { + const is_setter = target.endsWith('#set'); + target = !is_setter ? target : target.slice(0, -4); + const split = target.split('.'); + const fn_name = split.pop(); + const root_nm = split.splice(0,1)[0]; + const _eval = eval; // The browser doesn't expose all global variables (e.g. 'Game') inside globalThis, but it does to an eval. We copy it to a variable to have it run in global scope. + const 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 `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`; + + let original = null; + const wrapper = (chain ?? type != 'OVERRIDE') ? function() { return fn.call(this, original.bind(this), ...arguments); } : function() { return fn.apply(this, arguments); }; + + if(!is_setter) { + if(descriptor.value) { + original = descriptor.value; + descriptor.value = wrapper; + } + else { + original = descriptor.get; + descriptor.get = wrapper; + } + } + else { + if(!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`; + original = descriptor.set; + descriptor.set = wrapper; + } + + descriptor.configurable = true; + Object.defineProperty(obj, fn_name, descriptor); + } + } +}); diff --git a/module.json b/module.json index 9f806ec..519ae46 100644 --- a/module.json +++ b/module.json @@ -13,6 +13,7 @@ } ], "esmodules": [ + "lib/libwrapper_shim.js", "src/main.js" ], "languages": [ diff --git a/src/features/door_control_icon_scale.js b/src/features/door_control_icon_scale.js index b42bb6e..631c7c5 100644 --- a/src/features/door_control_icon_scale.js +++ b/src/features/door_control_icon_scale.js @@ -1,13 +1,14 @@ +import { libWrapper } from "../../lib/libwrapper_shim.js" import {settingsKey} from "../settings.js" // Adjust the repositioning formula for the door controls export function hookDoorControlReposition() { - DoorControl.prototype.reposition = function () { + libWrapper.register("smart-doors", "DoorControl.prototype.reposition", function () { let gridSize = this.wall.scene.data.grid gridSize *= game.settings.get(settingsKey, "doorControlSizeFactor") const pos = this.wall.midpoint.map(p => p - gridSize * 0.2) this.position.set(...pos) - } + }, "OVERRIDE"); } // Set the size of all door controls in relation to the grid size so it'll have a constant percieved size diff --git a/src/main.js b/src/main.js index 3f9e530..676fa20 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,6 @@ "use strict"; +import {libWrapper} from "../lib/libwrapper_shim.js"; import * as DoorControlIconScale from "./features/door_control_icon_scale.js" import * as DoorControlOutline from "./features/door_control_outline.js" import * as HighlightSecretDoors from "./features/highlight_secret_doors.js" @@ -35,45 +36,41 @@ Hooks.on("renderWallConfig", SynchronizedDoors.onRederWallConfig) // Hook the update function of the WallConfig dialog so we can store our custom data function hookWallConfigUpdate() { // Replace the original function with our custom one - const originalHandler = WallConfig.prototype._updateObject; - WallConfig.prototype._updateObject = async function (event, formData) { - await originalHandler.call(this, event, formData) + libWrapper.register("smart-doors", "WallConfig.prototype._updateObject", async function (wrapped, event, formData) { + await wrapped(event, formData); return SynchronizedDoors.onWallConfigUpdate.call(this, event, formData) - } + }, "WRAPPER"); } function hookDoorControlDraw() { - const originalHandler = DoorControl.prototype.draw - DoorControl.prototype.draw = async function () { - const result = await originalHandler.call(this) + libWrapper.register("smart-doors", "DoorControl.prototype.draw", async function (wrapped) { + const result = await wrapped(); DoorControlIconScale.onDoorControlPostDraw.call(this) DoorControlOutline.onDoorControlPostDraw.call(this) - return result - } + return result; + }, "WRAPPER"); } // Hook mouse events on DoorControls to perform our logic. // If we successfully handled the event block the original handler. Forward the event otherwise. function hookDoorEvents() { // Replace the original mousedown handler with our custom one - const originalMouseDownHandler = DoorControl.prototype._onMouseDown - DoorControl.prototype._onMouseDown = function (event) { + libWrapper.register("smart-doors", "DoorControl.prototype._onMouseDown", function (wrapped, event) { // Call our handler first. Only allow the original handler to run if our handler returns true const eventHandled = onDoorMouseDown.call(this, event) if (eventHandled) return - return originalMouseDownHandler.call(this, event) - } + return wrapped(event); + }, "MIXED"); // Replace the original rightdown handler with our custom one - const originalRightDownHandler = DoorControl.prototype._onRightDown - DoorControl.prototype._onRightDown = function (event) { + libWrapper.register("smart-doors", "DoorControl.prototype._onRightDown", function (wrapped, event) { // Call our handler first. Only allow the original handler to run if our handler returns true const eventHandled = onDoorRightDown.call(this, event) if (eventHandled) return - return originalRightDownHandler.call(this, event) - } + return wrapped(event); + }, "MIXED"); } // Our custom handler for mousedown events on doors