`
html.find(".form-group").last().after(synchronizedSettings)
const smartdoorsData = data.object.flags.smartdoors
// 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"})
}
})
// 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)
return onWallConfigUpdate.call(this, event, formData)
}
}
// Store our custom data from the WallConfig dialog
async function onWallConfigUpdate(event, formData) {
const updateData = {flags: {smartdoors: {synchronizationGroup: formData.synchronizationGroup}}}
let ids = this.options.editTargets;
if (ids.length == 0) {
ids = [this.object.data._id];
}
// If a synchronization group is set, get the state of existing doors and assume their state
if (formData.synchronizationGroup) {
const doorInGroup = findInAllWalls(wall => wall.door && wall.flags.smartdoors?.synchronizationGroup == formData.synchronizationGroup && !ids.includes(wall._id));
if (doorInGroup)
updateData.ds = doorInGroup.ds;
}
// Update all the edited walls
const updateDataset = ids.reduce((dataset, id) => {
dataset.push({_id: id, ...updateData})
return dataset
}, [])
return canvas.scene.updateEmbeddedEntity("Wall", updateDataset)
}
// 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) {
// 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)
}
// Replace the original rightdown handler with our custom one
const originalRightDownHandler = DoorControl.prototype._onRightDown
DoorControl.prototype._onRightDown = function (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)
}
}
// Searches through all scenes for walls and returns those that match the given filter criteria.
function filterAllWalls(filterFn) {
// Find all walls that match the filter criteria
const scenes = game.scenes.map((scene) => {return {scene: scene, walls: scene.data.walls.filter(filterFn)}})
// Drop all scenes that don't contain any results
return scenes.filter(scene => scene.walls.length > 0)
}
// Searches through all scenes for a wall that matches the given filter criteria
function findInAllWalls(filterFn) {
// TODO The performance of this could be increased by stopping the search on the first hit
const scenes = filterAllWalls(filterFn)
// If results were found take the first wall from the first scene.
return scenes[0]?.walls[0]
}
// Our custom handler for mousedown events on doors
function onDoorMouseDown(event) {
// If the user doesn't have the "door" permission we don't do anything.
if (!game.user.can("WALL_DOORS"))
return false
// If the game is paused don't do anything if the current player isn't the gm
if ( game.paused && !game.user.isGM )
return false
if (lockedDoorAlertLeftClick.call(this))
return true
if (synchronizedDoorsLeftClick.call(this))
return true
return false
}
// Our custom handler for rightdown events on doors
function onDoorRightDown(event) {
if (synchronizedDoorsRightClick.call(this))
return true
return false
}
// Creates a chat message stating that a player tried to open a locked door
function lockedDoorAlertLeftClick() {
const state = this.wall.data.ds
const states = CONST.WALL_DOOR_STATES
// Check if this feature is enabled
if (!game.settings.get(settingsKey, "lockedDoorAlert"))
return false
// Only create messages when the door is locked.
if (state != states.LOCKED)
return false
// Generate no message if the gm attempts to open the door
if (game.user.isGM)
return false
// Create and send the chat message
const message = {}
message.user = game.user
if (game.user.character)
message.speaker = {actor: game.user.character}
message.content = "Just tried to open a locked door"
message.sound = CONFIG.sounds.lock
message.flags = {smartdoors: {sourceId: this.wall.data._id}}
ChatMessage.create(message)
return true
}
// Updates all doors in the specified synchronization group with the provided data
function updateSynchronizedDoors(updateData, synchronizationGroup) {
// Search for doors belonging to the synchronization group in all scenes
let scenes = filterAllWalls(wall => wall.door && wall.flags.smartdoors?.synchronizationGroup == synchronizationGroup);
// Update all doors in the synchronization group
scenes.forEach((scene) => {
scene.scene.updateEmbeddedEntity("Wall", scene.walls.map((wall) => {return {_id: wall._id, ...updateData}}))
})
}
// Update the state of all synchronized doors
function synchronizedDoorsLeftClick() {
const state = this.wall.data.ds
const states = CONST.WALL_DOOR_STATES
// Check if this feature is enabled
if (!game.settings.get(settingsKey, "synchronizedDoors"))
return false
const synchronizationGroup = this.wall.data.flags.smartdoors?.synchronizationGroup
// Does this door have a synchronization group? If not there is nothing to do
if (!synchronizationGroup)
return false
// If the door is locked there is nothing to synchronize
if (this.state === states.LOCKED)
return false
// Calculate new door state
const newstate = state === states.CLOSED ? states.OPEN : states.CLOSED
// Update all doors belonging to the synchronization group
const updateData = {ds: newstate}
updateSynchronizedDoors(updateData, synchronizationGroup)
return true
}
function synchronizedDoorsRightClick() {
const state = this.wall.data.ds
const states = CONST.WALL_DOOR_STATES
// Check if this feature is enabled
if (!game.settings.get(settingsKey, "synchronizedDoors"))
return false
const synchronizationGroup = this.wall.data.flags.smartdoors?.synchronizationGroup
// Does this door have a synchronization group? If not there is nothing to do
if (!synchronizationGroup)
return false
// Only the gm is allowed to lock/unlock doors
if ( !game.user.isGM )
return false;
// If the door is currently opened we cannot lock the door
if ( state === states.OPEN )
return false;
// Calculate new door state
const newstate = state === states.LOCKED ? states.CLOSED : states.LOCKED;
// Update all doors belonging to the synchronization group
const updateData = {ds: newstate}
updateSynchronizedDoors(updateData, synchronizationGroup)
return true
}
function performMigrations() {
const dataVersion = game.settings.get(settingsKey, "dataVersion")
if (dataVersion === "fresh install")
{
game.settings.set(settingsKey, "dataVersion", currentDataVersion);
return;
}
if (dataVersion != currentDataVersion)
ui.notifications.error(game.i18n.localize("smart-doors.ui.messages.unknownVersion"), {permanent: true})
}
function registerSettings() {
game.settings.register(settingsKey, "dataVersion", {
scope: "world",
config: false,
type: String,
default: "fresh install"
})
game.settings.register(settingsKey, "lockedDoorAlert", {
name: "smart-doors.settings.lockedDoorAlert.name",
hint: "smart-doors.settings.lockedDoorAlert.hint",
scope: "world",
config: true,
type: Boolean,
default: true,
})
game.settings.register(settingsKey, "synchronizedDoors", {
name: "smart-doors.settings.synchronizedDoors.name",
hint: "smart-doors.settings.synchronizedDoors.hint",
scope: "world",
config: true,
type: Boolean,
default: true,
})
}