Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01857f6ef4 | |||
| 1215ddf55a | |||
| 2eca460637 | |||
| 3461db568e | |||
| 47c5eecd9c | |||
| 0f1975f9ae | |||
| aafa18a2c2 |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,3 +1,15 @@
|
|||||||
|
## 1.2.6
|
||||||
|
### 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
|
||||||
|
|
||||||
|
## v1.2.4
|
||||||
|
### Bugfix
|
||||||
|
- Fixed a race condition that may cause doors to not be properly synchronized across scenes
|
||||||
|
|
||||||
## v1.2.3
|
## v1.2.3
|
||||||
### Other
|
### Other
|
||||||
- Smart Doors is now compatible with Arms Reach
|
- Smart Doors is now compatible with Arms Reach
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
[](https://ko-fi.com/staebchenfisch)
|
||||||
|
|
||||||
# Smart Doors
|
# Smart Doors
|
||||||
Makes doors smarter. Allows doors to synchronize across multiple scenes and sends chat messages when players try to open locked doors (and also tells you which of the doors).
|
Makes doors smarter. Allows doors to synchronize across multiple scenes and sends chat messages when players try to open locked doors (and also tells you which of the doors).
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,8 @@
|
|||||||
},
|
},
|
||||||
"synchronizedDoors": {
|
"synchronizedDoors": {
|
||||||
"description": "State changes of doors in the same synchronization group will be synchronized across scenes. Leave blank to disable synchronization for this door.",
|
"description": "State changes of doors in the same synchronization group will be synchronized across scenes. Leave blank to disable synchronization for this door.",
|
||||||
"groupName": "Synchronization Group"
|
"groupName": "Synchronization Group",
|
||||||
|
"synchronizeSecretStatus": "Synchronize Secret Status"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
lib/libwrapper_shim.js
Normal file
61
lib/libwrapper_shim.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "smart-doors",
|
"name": "smart-doors",
|
||||||
"title": "Smart Doors",
|
"title": "Smart Doors",
|
||||||
"description": "Makes doors smarter. Allows doors to synchronize across multiple scenes and sends chat messages when players try to open locked doors.",
|
"description": "Makes doors smarter. Allows doors to synchronize across multiple scenes and sends chat messages when players try to open locked doors.",
|
||||||
"version": "1.2.3",
|
"version": "1.2.6",
|
||||||
"minimumCoreVersion" : "0.7.7",
|
"minimumCoreVersion" : "0.7.7",
|
||||||
"compatibleCoreVersion" : "0.7.9",
|
"compatibleCoreVersion" : "0.7.9",
|
||||||
"authors": [
|
"authors": [
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"esmodules": [
|
"esmodules": [
|
||||||
|
"lib/libwrapper_shim.js",
|
||||||
"src/main.js"
|
"src/main.js"
|
||||||
],
|
],
|
||||||
"languages": [
|
"languages": [
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
],
|
],
|
||||||
"url": "https://github.com/manuelVo/foundryvtt-smart-doors",
|
"url": "https://github.com/manuelVo/foundryvtt-smart-doors",
|
||||||
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-smart-doors/master/module.json",
|
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-smart-doors/master/module.json",
|
||||||
"download": "https://github.com/manuelVo/foundryvtt-smart-doors/archive/v1.2.3.zip",
|
"download": "https://github.com/manuelVo/foundryvtt-smart-doors/archive/v1.2.6.zip",
|
||||||
"readme": "https://github.com/manuelVo/foundryvtt-smart-doors/blob/master/README.md",
|
"readme": "https://github.com/manuelVo/foundryvtt-smart-doors/blob/master/README.md",
|
||||||
"changelog": "https://github.com/manuelVo/foundryvtt-smart-doors/blob/master/CHANGELOG.md",
|
"changelog": "https://github.com/manuelVo/foundryvtt-smart-doors/blob/master/CHANGELOG.md",
|
||||||
"bugs": "https://github.com/manuelVo/foundryvtt-smart-doors/issues"
|
"bugs": "https://github.com/manuelVo/foundryvtt-smart-doors/issues"
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
|
import { libWrapper } from "../../lib/libwrapper_shim.js"
|
||||||
import {settingsKey} from "../settings.js"
|
import {settingsKey} from "../settings.js"
|
||||||
|
|
||||||
// Adjust the repositioning formula for the door controls
|
// Adjust the repositioning formula for the door controls
|
||||||
export function hookDoorControlReposition() {
|
export function hookDoorControlReposition() {
|
||||||
DoorControl.prototype.reposition = function () {
|
libWrapper.register("smart-doors", "DoorControl.prototype.reposition", function () {
|
||||||
let gridSize = this.wall.scene.data.grid
|
let gridSize = this.wall.scene.data.grid
|
||||||
gridSize *= game.settings.get(settingsKey, "doorControlSizeFactor")
|
gridSize *= game.settings.get(settingsKey, "doorControlSizeFactor")
|
||||||
const pos = this.wall.midpoint.map(p => p - gridSize * 0.2)
|
const pos = this.wall.midpoint.map(p => p - gridSize * 0.2)
|
||||||
this.position.set(...pos)
|
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
|
// Set the size of all door controls in relation to the grid size so it'll have a constant percieved size
|
||||||
|
|||||||
@@ -11,13 +11,18 @@ export function onRederWallConfig(wallConfig, html, data) {
|
|||||||
<label for="synchronizationGroup">${game.i18n.localize("smart-doors.ui.synchronizedDoors.groupName")}</label>
|
<label for="synchronizationGroup">${game.i18n.localize("smart-doors.ui.synchronizedDoors.groupName")}</label>
|
||||||
<input type="text" name="synchronizationGroup"/>
|
<input type="text" name="synchronizationGroup"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="synchronizeSecretStatus">${game.i18n.localize("smart-doors.ui.synchronizedDoors.synchronizeSecretStatus")}</label>
|
||||||
|
<input type="checkbox" name="synchronizeSecretStatus" value="true"/>
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
html.find(".form-group").last().after(synchronizedSettings)
|
html.find(".form-group").last().after(synchronizedSettings)
|
||||||
|
|
||||||
const smartdoorsData = data.object.flags.smartdoors
|
const smartdoorsData = data.object.flags.smartdoors
|
||||||
// Fill the injected input fields with values
|
// Fill the injected input fields with values
|
||||||
const input = (name) => html.find(`input[name="${name}"]`)
|
const input = (name) => html.find(`input[name="${name}"]`); // input is a helper function to search for a input field by it's name
|
||||||
input("synchronizationGroup").prop("value", smartdoorsData?.synchronizationGroup)
|
input("synchronizationGroup").prop("value", smartdoorsData?.synchronizationGroup)
|
||||||
|
input("synchronizeSecretStatus").prop("checked", smartdoorsData?.synchronizeSecretStatus);
|
||||||
|
|
||||||
// Recalculate config window height
|
// Recalculate config window height
|
||||||
wallConfig.setPosition({height: "auto"})
|
wallConfig.setPosition({height: "auto"})
|
||||||
@@ -26,7 +31,8 @@ export function onRederWallConfig(wallConfig, html, data) {
|
|||||||
|
|
||||||
// Store our custom data from the WallConfig dialog
|
// Store our custom data from the WallConfig dialog
|
||||||
export async function onWallConfigUpdate(event, formData) {
|
export async function onWallConfigUpdate(event, formData) {
|
||||||
const updateData = {flags: {smartdoors: {synchronizationGroup: formData.synchronizationGroup}}}
|
const synchronizeSecretStatus = formData.synchronizeSecretStatus;
|
||||||
|
const updateData = {flags: {smartdoors: {synchronizationGroup: formData.synchronizationGroup}}};
|
||||||
let ids = this.options.editTargets;
|
let ids = this.options.editTargets;
|
||||||
if (ids.length == 0) {
|
if (ids.length == 0) {
|
||||||
ids = [this.object.data._id];
|
ids = [this.object.data._id];
|
||||||
@@ -34,6 +40,9 @@ export async function onWallConfigUpdate(event, formData) {
|
|||||||
|
|
||||||
// If a synchronization group is set, get the state of existing doors and assume their state
|
// If a synchronization group is set, get the state of existing doors and assume their state
|
||||||
if (formData.synchronizationGroup) {
|
if (formData.synchronizationGroup) {
|
||||||
|
// Update the synchronizeSecretStatus flag
|
||||||
|
updateData.flags.smartdoors.synchronizeSecretStatus = synchronizeSecretStatus;
|
||||||
|
|
||||||
// Search for other doors in the synchronization group that aren't in the list of edited doors
|
// Search for other doors in the synchronization group that aren't in the list of edited doors
|
||||||
const doorInGroup = Util.findInAllWalls(wall => {
|
const doorInGroup = Util.findInAllWalls(wall => {
|
||||||
// We only search for doors
|
// We only search for doors
|
||||||
@@ -47,8 +56,15 @@ export async function onWallConfigUpdate(event, formData) {
|
|||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
if (doorInGroup)
|
if (doorInGroup) {
|
||||||
|
// ds is the door sate in foundry
|
||||||
updateData.ds = doorInGroup.ds;
|
updateData.ds = doorInGroup.ds;
|
||||||
|
|
||||||
|
if (synchronizeSecretStatus) {
|
||||||
|
// door is the door type in foundry
|
||||||
|
updateData.door = doorInGroup.door
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update all the edited walls
|
// Update all the edited walls
|
||||||
@@ -56,7 +72,13 @@ export async function onWallConfigUpdate(event, formData) {
|
|||||||
dataset.push({_id: id, ...updateData})
|
dataset.push({_id: id, ...updateData})
|
||||||
return dataset
|
return dataset
|
||||||
}, [])
|
}, [])
|
||||||
return canvas.scene.updateEmbeddedEntity("Wall", updateDataset)
|
const updateResult = await canvas.scene.updateEmbeddedEntity("Wall", updateDataset);
|
||||||
|
|
||||||
|
// If door is synchronized, synchronize secret status among synchronized doors
|
||||||
|
if (formData.synchronizationGroup)
|
||||||
|
await updateSynchronizedDoors(updateData, formData.synchronizationGroup);
|
||||||
|
|
||||||
|
return updateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the state of all synchronized doors
|
// Update the state of all synchronized doors
|
||||||
@@ -121,12 +143,13 @@ export function onDoorRightClick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Updates all doors in the specified synchronization group with the provided data
|
// Updates all doors in the specified synchronization group with the provided data
|
||||||
function updateSynchronizedDoors(updateData, synchronizationGroup) {
|
export async function updateSynchronizedDoors(updateData, synchronizationGroup) {
|
||||||
// Search for doors belonging to the synchronization group in all scenes
|
// Search for doors belonging to the synchronization group in all scenes
|
||||||
let scenes = Util.filterAllWalls(wall => wall.door && wall.flags.smartdoors?.synchronizationGroup === synchronizationGroup);
|
let scenes = Util.filterAllWalls(wall => wall.door && wall.flags.smartdoors?.synchronizationGroup === synchronizationGroup);
|
||||||
|
|
||||||
// Update all doors in the synchronization group
|
// Update all doors in the synchronization group
|
||||||
scenes.forEach((scene) => {
|
for (const scene of scenes) {
|
||||||
scene.scene.updateEmbeddedEntity("Wall", scene.walls.map((wall) => {return {_id: wall._id, ...updateData}}))
|
// When VFTT 0.8 is out look for a way to do this in a single call.
|
||||||
})
|
await scene.scene.updateEmbeddedEntity("Wall", scene.walls.map((wall) => {return {_id: wall._id, ...updateData}}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {settingsKey} from "../settings.js"
|
import {settingsKey} from "../settings.js"
|
||||||
|
import {updateSynchronizedDoors} from "./synchronized_doors.js";
|
||||||
|
|
||||||
// Toggles between normal and secret doors
|
// Toggles between normal and secret doors
|
||||||
export function onDoorLeftClick(event) {
|
export function onDoorLeftClick(event) {
|
||||||
@@ -6,7 +7,13 @@ export function onDoorLeftClick(event) {
|
|||||||
if (game.settings.get(settingsKey, "toggleSecretDoors") && event.data?.originalEvent?.ctrlKey && game.user.isGM) {
|
if (game.settings.get(settingsKey, "toggleSecretDoors") && event.data?.originalEvent?.ctrlKey && game.user.isGM) {
|
||||||
const types = CONST.WALL_DOOR_TYPES
|
const types = CONST.WALL_DOOR_TYPES
|
||||||
const newtype = this.wall.data.door === types.DOOR ? types.SECRET : types.DOOR
|
const newtype = this.wall.data.door === types.DOOR ? types.SECRET : types.DOOR
|
||||||
this.wall.update({door: newtype})
|
const updateData = {door: newtype}
|
||||||
|
const synchronizationGroup = this.wall.data.flags.smartdoors?.synchronizationGroup
|
||||||
|
if (game.settings.get(settingsKey, "synchronizedDoors") && synchronizationGroup && this.wall.data.flags.smartdoors?.synchronizeSecretStatus)
|
||||||
|
updateSynchronizedDoors(updateData, synchronizationGroup)
|
||||||
|
else
|
||||||
|
this.wall.update(updateData)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
31
src/main.js
31
src/main.js
@@ -1,5 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
import {libWrapper} from "../lib/libwrapper_shim.js";
|
||||||
import * as DoorControlIconScale from "./features/door_control_icon_scale.js"
|
import * as DoorControlIconScale from "./features/door_control_icon_scale.js"
|
||||||
import * as DoorControlOutline from "./features/door_control_outline.js"
|
import * as DoorControlOutline from "./features/door_control_outline.js"
|
||||||
import * as HighlightSecretDoors from "./features/highlight_secret_doors.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
|
// Hook the update function of the WallConfig dialog so we can store our custom data
|
||||||
function hookWallConfigUpdate() {
|
function hookWallConfigUpdate() {
|
||||||
// Replace the original function with our custom one
|
// Replace the original function with our custom one
|
||||||
const originalHandler = WallConfig.prototype._updateObject;
|
libWrapper.register("smart-doors", "WallConfig.prototype._updateObject", async function (wrapped, event, formData) {
|
||||||
WallConfig.prototype._updateObject = async function (event, formData) {
|
await wrapped(event, formData);
|
||||||
await originalHandler.call(this, event, formData)
|
|
||||||
return SynchronizedDoors.onWallConfigUpdate.call(this, event, formData)
|
return SynchronizedDoors.onWallConfigUpdate.call(this, event, formData)
|
||||||
}
|
}, "WRAPPER");
|
||||||
}
|
}
|
||||||
|
|
||||||
function hookDoorControlDraw() {
|
function hookDoorControlDraw() {
|
||||||
const originalHandler = DoorControl.prototype.draw
|
libWrapper.register("smart-doors", "DoorControl.prototype.draw", async function (wrapped) {
|
||||||
DoorControl.prototype.draw = async function () {
|
const result = await wrapped();
|
||||||
const result = await originalHandler.call(this)
|
|
||||||
DoorControlIconScale.onDoorControlPostDraw.call(this)
|
DoorControlIconScale.onDoorControlPostDraw.call(this)
|
||||||
DoorControlOutline.onDoorControlPostDraw.call(this)
|
DoorControlOutline.onDoorControlPostDraw.call(this)
|
||||||
return result
|
return result;
|
||||||
}
|
}, "WRAPPER");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook mouse events on DoorControls to perform our logic.
|
// Hook mouse events on DoorControls to perform our logic.
|
||||||
// If we successfully handled the event block the original handler. Forward the event otherwise.
|
// If we successfully handled the event block the original handler. Forward the event otherwise.
|
||||||
function hookDoorEvents() {
|
function hookDoorEvents() {
|
||||||
// Replace the original mousedown handler with our custom one
|
// Replace the original mousedown handler with our custom one
|
||||||
const originalMouseDownHandler = DoorControl.prototype._onMouseDown
|
libWrapper.register("smart-doors", "DoorControl.prototype._onMouseDown", function (wrapped, event) {
|
||||||
DoorControl.prototype._onMouseDown = function (event) {
|
|
||||||
// Call our handler first. Only allow the original handler to run if our handler returns true
|
// Call our handler first. Only allow the original handler to run if our handler returns true
|
||||||
const eventHandled = onDoorMouseDown.call(this, event)
|
const eventHandled = onDoorMouseDown.call(this, event)
|
||||||
if (eventHandled)
|
if (eventHandled)
|
||||||
return
|
return
|
||||||
return originalMouseDownHandler.call(this, event)
|
return wrapped(event);
|
||||||
}
|
}, "MIXED");
|
||||||
|
|
||||||
// Replace the original rightdown handler with our custom one
|
// Replace the original rightdown handler with our custom one
|
||||||
const originalRightDownHandler = DoorControl.prototype._onRightDown
|
libWrapper.register("smart-doors", "DoorControl.prototype._onRightDown", function (wrapped, event) {
|
||||||
DoorControl.prototype._onRightDown = function (event) {
|
|
||||||
// Call our handler first. Only allow the original handler to run if our handler returns true
|
// Call our handler first. Only allow the original handler to run if our handler returns true
|
||||||
const eventHandled = onDoorRightDown.call(this, event)
|
const eventHandled = onDoorRightDown.call(this, event)
|
||||||
if (eventHandled)
|
if (eventHandled)
|
||||||
return
|
return
|
||||||
return originalRightDownHandler.call(this, event)
|
return wrapped(event);
|
||||||
}
|
}, "MIXED");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our custom handler for mousedown events on doors
|
// Our custom handler for mousedown events on doors
|
||||||
|
|||||||
Reference in New Issue
Block a user