From cf2d3d8a3478586360c620265c302ef7a487aa67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20V=C3=B6gele?= Date: Wed, 16 Dec 2020 12:29:33 +0100 Subject: [PATCH] Initial macro code --- CHANGELOG.md | 2 ++ lang/en.json | 19 +++++++++++ src/features/execute_macro.js | 55 ++++++++++++++++++++++++++++++ src/features/synchronized_doors.js | 3 -- src/form.js | 38 +++++++++++++++++++++ src/main.js | 18 ++++++++-- src/settings.js | 8 +++++ 7 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 src/features/execute_macro.js create mode 100644 src/form.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 88218db..fa252b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ ## v1.1.0 ### New features - Draw outlines around Door Control icons to increase their visibility +- Execute a macro when someone interacts with a door ### Other - Secret doors are now tinted black instead of dark grey. +- Setting hints will now be shown below the title of the setting (before it was above) ### New features - Tint secret doors grey for the GM to differentiate them from regular doors diff --git a/lang/en.json b/lang/en.json index 00a9dd3..9c21042 100644 --- a/lang/en.json +++ b/lang/en.json @@ -17,6 +17,10 @@ "name": "Locked Door Alert", "hint": "Send a message in chat when a player tried to open a locked door" }, + "macros": { + "name": "Door Interaction Macros", + "hint": "Trigger a macro when a door is being interacted with" + }, "synchronizedDoors": { "name": "Synchronized Doors", "hint": "Synchronize the state of configured doors" @@ -27,7 +31,22 @@ } }, "ui": { + "form": { + "macroExecuteEverywhere": { + "name": "Execute on all clients", + "hint": "If disabled the macro will be executed on the GMs client" + }, + "macroName": { + "name": "Macro Name", + "hint": "The name of the macro that should be executed when this door is interacted with. No macro is executed if left blank." + }, + "macroArguments": { + "name": "Marco Parameters", + "hint": "The parameters passed to the macro. Any JSON string is valid." + } + }, "messages": { + "argsInvalidJson": "The macro arguments must be valid JSON. See console for details.", "migrating": "Migrating Smart Doors to version {version}. Please don't close the application.", "migrationDone": "Smart Doors successfully migrated to version {version}.", "unknownVersion": "Smart Doors migration failed with the error: Unkown Version {version}. Please report this to the Smart Doors issue tracker. To prevent possible data loss don't use this plugin until this error is fixed." diff --git a/src/features/execute_macro.js b/src/features/execute_macro.js new file mode 100644 index 0000000..61352bf --- /dev/null +++ b/src/features/execute_macro.js @@ -0,0 +1,55 @@ +import {settingsKey} from "../settings.js" +import {textInput, checkboxInput, injectSettings} from "../form.js" + +// Inject settings for synchronized doors +export function onRederWallConfig(wallConfig, html, data) { + if (data.isDoor && game.settings.get(settingsKey, "macros")) { + + const settings = [ + textInput("macroName", data.object.flags.smartdoors?.macro?.name), + textInput("macroArguments", JSON.stringify(data.object.flags.smartdoors?.macro?.args ?? undefined)), + checkboxInput("macroExecuteEverywhere", data.object.flags.smartdoors?.macro?.executeEverywhere), + ] + + injectSettings(html, settings) + } +} + +// Check data input by the user for validity +export async function onWallConfigPreUpdate(event, formData) { + const args = formData.macroArguments || "null" + + try { + // Check if args can be converted to JSON + JSON.parse(args) + } + catch (error) { + ui.notifications.error(game.i18n.localize("smart-doors.ui.messages.argsInvalidJson")) + // Rethrow the error to stop the update and prevent the dialog from closing + throw(error) + } + + // The JSON is valid. Assign "null" instead of an empty string if necessary + formData.macroArguments = args +} + +// Store our custom data from the WallConfig dialog +export async function onWallConfigUpdate(event, formData) { + let ids = this.options.editTargets; + if (ids.length == 0) { + ids = [this.object.data._id]; + } + + const updateData = {flags: {smartdoors: {macro: { + name: formData.macroName, + args: JSON.parse(formData.macroArguments), + executeEverywhere: formData.macroExecuteEverywhere + }}}} + + // Update all the edited walls + const updateDataset = ids.reduce((dataset, id) => { + dataset.push({_id: id, ...updateData}) + return dataset + }, []) + return canvas.scene.updateEmbeddedEntity("Wall", updateDataset) +} diff --git a/src/features/synchronized_doors.js b/src/features/synchronized_doors.js index 0322c2b..e53a00d 100644 --- a/src/features/synchronized_doors.js +++ b/src/features/synchronized_doors.js @@ -18,9 +18,6 @@ export function onRederWallConfig(wallConfig, html, data) { // Fill the injected input fields with values const input = (name) => html.find(`input[name="${name}"]`) input("synchronizationGroup").prop("value", smartdoorsData?.synchronizationGroup) - - // Recalculate config window height - wallConfig.setPosition({height: "auto"}) } } diff --git a/src/form.js b/src/form.js new file mode 100644 index 0000000..7954592 --- /dev/null +++ b/src/form.js @@ -0,0 +1,38 @@ +function formEntry(name, input) { + return ` +
+ + ${input} +
+

${game.i18n.localize(`smart-doors.ui.form.${name}.hint`)}

+ ` +} + +export function injectSettings(html, settings) { + html.find(".form-group").last().after(settings.join("")) +} + +export function textInput(name, value) { + return formEntry(name, ``) +} + +export function selectInput(name, values) { + // TODO Set selected option + let html = `" + return formEntry(name, html) +} + +export function checkboxInput(name, checked) { + return formEntry(name, ``) +} + +function escapeHtml(unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} diff --git a/src/main.js b/src/main.js index 7f927f7..c44d345 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,7 @@ import * as DoorControlIconScale from "./features/door_control_icon_scale.js" import * as DoorControlOutline from "./features/door_control_outline.js" +import * as ExecuteMacro from "./features/execute_macro.js" import * as HighlightSecretDoors from "./features/highlight_secret_doors.js" import * as LockedDoorAlert from "./features/locked_door_alert.js" import * as SynchronizedDoors from "./features/synchronized_doors.js" @@ -30,15 +31,28 @@ Hooks.on("canvasReady", HighlightSecretDoors.onCanvasReady) Hooks.on("updateWall", HighlightSecretDoors.onUpdateWall) // Inject our custom settings into the WallConfig dialog -Hooks.on("renderWallConfig", SynchronizedDoors.onRederWallConfig) +Hooks.on("renderWallConfig", (wallConfig, html, data) => { + SynchronizedDoors.onRederWallConfig(wallConfig, html, data) + ExecuteMacro.onRederWallConfig(wallConfig, html, data) + + // Recalculate config window position and height + wallConfig.element[0].style.top = "" // This forces foundry to re-calculate the top position + wallConfig.setPosition({height: "auto"}) +}) // 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 ExecuteMacro.onWallConfigPreUpdate.call(this, event, formData) + await originalHandler.call(this, event, formData) - return SynchronizedDoors.onWallConfigUpdate.call(this, event, formData) + + return Promise.all([ + SynchronizedDoors.onWallConfigUpdate.call(this, event, formData), + ExecuteMacro.onWallConfigUpdate.call(this, event, formData), + ]) } } diff --git a/src/settings.js b/src/settings.js index a036c1d..651b638 100644 --- a/src/settings.js +++ b/src/settings.js @@ -63,4 +63,12 @@ export function registerSettings() { type: Boolean, default: true, }) + game.settings.register(settingsKey, "macros", { + name: "smart-doors.settings.macros.name", + hint: "smart-doors.settings.macros.hint", + scope: "world", + config: true, + type: Boolean, + default: true, + }) }