Skip to content

Commit

Permalink
Use libwrapper for hooking
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelVo committed Jan 14, 2022
1 parent 219a6ad commit e406c0f
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 51 deletions.
79 changes: 79 additions & 0 deletions lib/libwrapper_shim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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,11,0];
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}={}) {
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 `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() { 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);
}
}
});
1 change: 1 addition & 0 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
}
],
"esmodules": [
"lib/libwrapper_shim.js",
"src/main.js"
],
"languages": [
Expand Down
100 changes: 49 additions & 51 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {libWrapper} from "../lib/libwrapper_shim.js";
import {getPixelsFromGridPosition} from "./foundry_fixes.js"
import {measureDistances, getCostEnhancedTerrainlayer} from "./measure.js"

Expand Down Expand Up @@ -44,67 +45,64 @@ Hooks.on("getSceneControlButtons", controls => {
})

function hookFunctions() {
const originalCanvasOnDragLeftStartHandler = Canvas.prototype._onDragLeftStart
Canvas.prototype._onDragLeftStart = function (event) {
const layer = this.activeLayer
const isRuler = game.activeTool === "ruler"
const isCtrlRuler = game.keyboard.isModifierActive(KeyboardManager.MODIFIER_KEYS.CONTROL) && (layer.name === "TokenLayer")
if (terrainRuler.active && (isRuler || isCtrlRuler)) {
// Show Terrain
if (game.settings.get("enhanced-terrain-layer", "show-on-drag"))
canvas.terrain.visible = true;

// Start measuring
const ruler = this.controls.ruler
ruler.isTerrainRuler = true
return ruler._onDragStart(event)
}
return originalCanvasOnDragLeftStartHandler.call(this, event)
libWrapper.register("terrain-ruler", "Canvas.prototype._onDragLeftStart", onDragLeftStart, "MIXED");
libWrapper.register("terrain-ruler", "Ruler.prototype._endMeasurement", endMeasurement, "WRAPPER");
libWrapper.register("terrain-ruler", "Ruler.prototype._highlightMeasurement", highlightMeasurement, "MIXED");
libWrapper.register("terrain-ruler", "Ruler.prototype.toJSON", toJSON, "WRAPPER");
libWrapper.register("terrain-ruler", "Ruler.prototype.update", rulerUpdate, "WRAPPER");
libWrapper.register("terrain-ruler", "GridLayer.prototype.measureDistances", gridLayerMeasureDistances, "MIXED");
}

function onDragLeftStart(wrapped, event) {
const layer = this.activeLayer;
const isRuler = game.activeTool === "ruler";
const isCtrlRuler = game.keyboard.isModifierActive(KeyboardManager.MODIFIER_KEYS.CONTROL) && (layer.name === "TokenLayer");
if (terrainRuler.active && (isRuler || isCtrlRuler)) {
// Show Terrain
if (game.settings.get("enhanced-terrain-layer", "show-on-drag"))
canvas.terrain.visible = true;

// Start measuring
const ruler = this.controls.ruler;
ruler.isTerrainRuler = true;
return ruler._onDragStart(event);
}
return wrapped(event);
}

const originalEndMeasurementHandler = Ruler.prototype._endMeasurement
Ruler.prototype._endMeasurement = function (event) {
// Reset terrain visiblility to default state
canvas.terrain.visible = (canvas.terrain.showterrain || ui.controls.activeControl == "terrain");
function endMeasurement(wrapped, event) {
// Reset terrain visiblility to default state
canvas.terrain.visible = (canvas.terrain.showterrain || ui.controls.activeControl == "terrain");

this.isTerrainRuler = false
return originalEndMeasurementHandler.call(this)
}
this.isTerrainRuler = false;
return wrapped(event);
}

const originalRulerHighlightMeasurement = Ruler.prototype._highlightMeasurement
Ruler.prototype._highlightMeasurement = function (ray) {
if (ray.terrainRulerVisitedSpaces)
highlightMeasurement.call(this, ray)
else
originalRulerHighlightMeasurement.call(this, ray)
function highlightMeasurement(wrapped, ray) {
if (!ray.terrainRulerVisitedSpaces) {
return wrapped(ray);
}

const originalRulerToJSON = Ruler.prototype.toJSON
Ruler.prototype.toJSON = function () {
const json = originalRulerToJSON.call(this)
json["isTerrainRuler"] = this.isTerrainRuler
return json
for (const space of ray.terrainRulerVisitedSpaces) {
const [x, y] = getPixelsFromGridPosition(space.x, space.y);
canvas.grid.highlightPosition(this.name, {x, y, color: this.color});
}
}

const originalRulerUpdate = Ruler.prototype.update
Ruler.prototype.update = function (data) {
this.isTerrainRuler = data.isTerrainRuler
originalRulerUpdate.call(this, data)
}
function toJSON(wrapped) {
const json = wrapped();
json["isTerrainRuler"] = this.isTerrainRuler;
return json;
}

const originalGridLayerMeasureDistances = GridLayer.prototype.measureDistances
GridLayer.prototype.measureDistances = function (segments, options={}) {
if (!options.enableTerrainRuler)
return originalGridLayerMeasureDistances.call(this, segments, options)
return measureDistances(segments)
}
function rulerUpdate(wrapped, data) {
this.isTerrainRuler = data.isTerrainRuler;
wrapped(data);
}

function highlightMeasurement(ray) {
for (const space of ray.terrainRulerVisitedSpaces) {
const [x, y] = getPixelsFromGridPosition(space.x, space.y);
canvas.grid.highlightPosition(this.name, {x, y, color: this.color})
}
function gridLayerMeasureDistances(wrapped, segments, options={}) {
if (!options.enableTerrainRuler)
return wrapped(segments, options);
return measureDistances(segments);
}

function strInsertAfter(haystack, needle, strToInsert) {
Expand Down

0 comments on commit e406c0f

Please sign in to comment.