diff --git a/lang/en.json b/lang/en.json index b4b1410e..817fc355 100755 --- a/lang/en.json +++ b/lang/en.json @@ -3220,7 +3220,8 @@ "domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used", "knowTheTide": "Know The Tide gained a token", "lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}", - "noTokenTargeted": "No token is targeted" + "noTokenTargeted": "No token is targeted", + "behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM" }, "Progress": { "migrationLabel": "Performing system migration. Please wait and do not close Foundry." diff --git a/module/canvas/placeables/regionLayer.mjs b/module/canvas/placeables/regionLayer.mjs index c53cf782..684fdd5e 100644 --- a/module/canvas/placeables/regionLayer.mjs +++ b/module/canvas/placeables/regionLayer.mjs @@ -57,14 +57,14 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer { } async placeRegion(data, options = {}) { - const preConfirm = ({ _event, document, _create, _options }) => { - const shape = document.shapes[0]; + const preConfirm = data => { + const shape = data.document.shapes[0]; const isEmanation = shape.type === 'emanation'; if (isEmanation) { const token = this.#findTokenInBounds(shape.base.origin); - if (!token) return options.preConfirm?.() ?? true; + if (!token) return options.preConfirm?.(data) ?? true; const shapeData = shape.toObject(); - document.updateSource({ + data.document.updateSource({ shapes: [ { ...shapeData, @@ -80,10 +80,10 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer { }); } - return options?.preConfirm?.() ?? true; + return options?.preConfirm?.(data) ?? true; }; - super.placeRegion(data, { ...options, preConfirm }); + return await super.placeRegion(data, { ...options, preConfirm }); } /** Searches for token at origin point, returning null if there are no tokens or multiple overlapping tokens */ diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 2e20fb87..d6877884 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -1,4 +1,4 @@ -import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs'; +import { emitAsGM, emitGMCreate, GMUpdateEvent } from '../systemRegistration/socket.mjs'; export default class DhpChatMessage extends foundry.documents.ChatMessage { targetHook = null; @@ -258,28 +258,51 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid); const { shape: type, size: range } = selectedArea; const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range }); + + const scene = game.scenes.get(game.user.viewedScene); + const level = scene.levels.find(x => x.isView); - await canvas.regions.placeRegion( - { - name: selectedArea.name, - shapes: [shapeData], - restriction: { enabled: false, type: 'move', priority: 0 }, - behaviors: [ - { - name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'), - type: 'applyActiveEffect', - system: { - effects: effects - } + const regionData = { + name: selectedArea.name, + levels: level ? [level.id] : [], + shapes: [shapeData], + restriction: { enabled: false, type: 'move', priority: 0 }, + behaviors: effects.length > 0 ? [ + { + name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'), + type: 'applyActiveEffect', + system: { + effects: effects } - ], - displayMeasurements: true, - locked: false, - ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE }, - visibility: CONST.REGION_VISIBILITY.ALWAYS - }, - { create: true } - ); + }] : [], + displayMeasurements: true, + locked: false, + ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE }, + visibility: CONST.REGION_VISIBILITY.ALWAYS + }; + const placeRegion = (data) => { + canvas.regions.placeRegion( + data, + { create: true } + ); + }; + + const needsGMExecution = effects.length > 0; + + if (needsGMExecution && !game.user.isGM) { + if (!game.users.activeGM) + return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.behaviorRegionRequiresGM')); + + const region = await canvas.regions.placeRegion(regionData, { create: false }); + emitGMCreate( + 'Region', + placeRegion, + region, + scene.id, + ); + } else { + placeRegion(regionData); + } }; if (this.system.action.areas.length === 1) createArea(this.system.action.areas[0]); diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index 8fed346d..6856150c 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -6,6 +6,9 @@ export function handleSocketEvent({ action = null, data = {} } = {}) { case socketEvent.GMUpdate: Hooks.callAll(socketEvent.GMUpdate, data); break; + case socketEvent.GMCreate: + Hooks.callAll(socketEvent.GMCreate, data); + break; case socketEvent.DhpFearUpdate: Hooks.callAll(socketEvent.DhpFearUpdate); break; @@ -25,6 +28,7 @@ export function handleSocketEvent({ action = null, data = {} } = {}) { export const socketEvent = { GMUpdate: 'DhGMUpdate', + GMCreate: 'DhGMCreate', Refresh: 'DhRefresh', DhpFearUpdate: 'DhFearUpdate', DowntimeTrigger: 'DowntimeTrigger', @@ -38,7 +42,7 @@ export const GMUpdateEvent = { UpdateSetting: 'DhGMUpdateSetting', UpdateFear: 'DhGMUpdateFear', UpdateCountdowns: 'DhGMUpdateCountdowns', - UpdateSaveMessage: 'DhGMUpdateSaveMessage' + UpdateSaveMessage: 'DhGMUpdateSaveMessage', }; export const RefreshType = { @@ -56,14 +60,14 @@ export const registerSocketHooks = () => { const document = data.uuid ? await fromUuid(data.uuid) : null; switch (data.action) { case GMUpdateEvent.UpdateDocument: - if (document && data.update) await document.update(data.update); + if (document && data.data) await document.update(data.data); break; case GMUpdateEvent.UpdateEffect: - if (document && data.update) - await game.system.api.fields.ActionFields.EffectsField.applyEffects.call(document, data.update); + if (document && data.data) + await game.system.api.fields.ActionFields.EffectsField.applyEffects.call(document, data.data); break; case GMUpdateEvent.UpdateSetting: - await game.settings.set(CONFIG.DH.id, data.uuid, data.update); + await game.settings.set(CONFIG.DH.id, data.uuid, data.data); break; case GMUpdateEvent.UpdateFear: await game.settings.set( @@ -73,22 +77,22 @@ export const registerSocketHooks = () => { 0, Math.min( game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear, - data.update + data.data ) ) ); break; case GMUpdateEvent.UpdateCountdowns: - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data.update); + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data.data); Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); break; case GMUpdateEvent.UpdateSaveMessage: - const message = game.messages.get(data.update.message); + const message = game.messages.get(data.data.message); if (!message) return; game.system.api.fields.ActionFields.SaveField.updateSaveMessage( - data.update.result, + data.data.result, message, - data.update.token + data.data.token ); break; } @@ -102,6 +106,17 @@ export const registerSocketHooks = () => { } } }); + + Hooks.on(socketEvent.GMCreate, async ({ data, documentType, scene }) => { + if (!game.user.isGM) return; + + switch (documentType) { + default: + const cls = getDocumentClass(documentType); + cls.create(data, { parent: game.scenes.get(scene) }); + break; + } + }); }; export const registerUserQueries = () => { @@ -109,18 +124,21 @@ export const registerUserQueries = () => { CONFIG.queries.reactionRoll = game.system.api.fields.ActionFields.SaveField.rollSaveQuery; }; -export const emitAsGM = async (eventName, callback, update, uuid = null, refresh = null) => { +export const emitGMUpdate = async (eventName, callback, update, uuid = null, refresh = null) => { + return await emitAsGM(socketEvent.GMUpdate, { eventName, callback, data: update, uuid, refresh }); +}; + +export const emitGMCreate = async (documentType, callback, data, scene) => { + return await emitAsGM(socketEvent.GMCreate, { documentType, callback, data, scene }); +}; + +export const emitAsGM = async (event, data = { callback: () => {}, data: {} }) => { if (!game.user.isGM) { return await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.GMUpdate, - data: { - action: eventName, - uuid, - update, - refresh - } + action: event, + data: data }); - } else return callback(update); + } else return data.callback(data.data); }; export const emitAsOwner = (eventName, userId, args) => {