[Fix] Player Created Regions (#1855)

* Fixed so that creating regions without behaviors work for players. Fixed so that creating regions with behaviors works via GmEmit for players

* Updated previous uses of emitAsGM to emitGMUpdate

* Fixed linting

* Update module/documents/chatMessage.mjs

Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com>

---------

Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com>
This commit is contained in:
WBHarry 2026-05-02 22:34:53 +02:00 committed by GitHub
parent 54d1b2bdc0
commit edbf5aa55f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 108 additions and 69 deletions

View file

@ -1,5 +1,5 @@
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import Party from '../sheets/actors/party.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -242,7 +242,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
});
};
await emitAsGM(
await emitGMUpdate(
GMUpdateEvent.UpdateDocument,
gmUpdate,
update,

View file

@ -1,6 +1,6 @@
import { MemberData } from '../../data/tagTeamData.mjs';
import { getCritDamageBonus } from '../../helpers/utils.mjs';
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import Party from '../sheets/actors/party.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -259,7 +259,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
});
};
await emitAsGM(
await emitGMUpdate(
GMUpdateEvent.UpdateDocument,
gmUpdate,
update,

View file

@ -1,6 +1,6 @@
import { DhCountdown } from '../../data/countdowns.mjs';
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -114,7 +114,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
}
await this.data.updateSource(update);
await emitAsGM(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
refreshType: RefreshType.Countdown
});

View file

@ -1,5 +1,5 @@
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -204,7 +204,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
start: newMax
}
});
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
refreshType: RefreshType.Countdown
});
}
@ -218,7 +218,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
? Math.min(countdown.progress.current + 1, countdown.progress.start)
: Math.max(countdown.progress.current - 1, 0);
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
refreshType: RefreshType.Countdown
});
}
@ -277,7 +277,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
return acc;
}, {})
};
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
refreshType: RefreshType.Countdown
});
}

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
import { emitGMUpdate, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -104,7 +104,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
}
async updateFear(value) {
return emitAsGM(
return emitGMUpdate(
GMUpdateEvent.UpdateFear,
game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
value

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
import { emitGMUpdate, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation {
/** @inheritdoc */
@ -68,7 +68,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
1
)[0];
newEnvironments.unshift(newFirst);
emitAsGM(
emitGMUpdate(
GMUpdateEvent.UpdateDocument,
scene.update.bind(scene),
{ 'flags.daggerheart.sceneEnvironments': newEnvironments },

View file

@ -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 */

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../../systemRegistration/socket.mjs';
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../../systemRegistration/socket.mjs';
const fields = foundry.data.fields;
@ -78,7 +78,7 @@ export default class CountdownField extends fields.ArrayField {
);
}
await emitAsGM(
await emitGMUpdate(
GMUpdateEvent.UpdateCountdowns,
async () => {
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent } from '../../../systemRegistration/socket.mjs';
import { emitGMUpdate, GMUpdateEvent } from '../../../systemRegistration/socket.mjs';
const fields = foundry.data.fields;
@ -34,7 +34,7 @@ export default class EffectsField extends fields.ArrayField {
}
if (EffectsField.getAutomation() || force) {
targets ??= (message.system?.targets ?? config.targets).filter(t => !config.hasRoll || t.hit);
await emitAsGM(GMUpdateEvent.UpdateEffect, EffectsField.applyEffects.bind(this), targets, this.uuid);
await emitGMUpdate(GMUpdateEvent.UpdateEffect, EffectsField.applyEffects.bind(this), targets, this.uuid);
// EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit));
}
}

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import { emitGMUpdate, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs';
import { createScrollText, damageKeyToNumber, getDamageKey, createShallowProxy } from '../helpers/utils.mjs';
@ -827,7 +827,7 @@ export default class DhpActor extends Actor {
const u = updates[key];
if (key === 'items') {
Object.values(u).forEach(async item => {
await emitAsGM(
await emitGMUpdate(
GMUpdateEvent.UpdateDocument,
item.target.update.bind(item.target),
item.resources,
@ -836,7 +836,7 @@ export default class DhpActor extends Actor {
});
} else {
if (Object.keys(u.resources).length > 0) {
await emitAsGM(
await emitGMUpdate(
GMUpdateEvent.UpdateDocument,
u.target.update.bind(u.target),
u.resources,

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import { emitGMUpdate, emitGMCreate, GMUpdateEvent } from '../systemRegistration/socket.mjs';
export default class DhpChatMessage extends foundry.documents.ChatMessage {
targetHook = null;
@ -214,7 +214,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
const action = this.system.action;
if (!action || !action?.hasSave) return;
game.system.api.fields.ActionFields.SaveField.rollSave.call(action, token.actor, event).then(result =>
emitAsGM(
emitGMUpdate(
GMUpdateEvent.UpdateSaveMessage,
game.system.api.fields.ActionFields.SaveField.updateSaveMessage.bind(
action,
@ -259,27 +259,47 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
const { shape: type, size: range } = selectedArea;
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range });
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
}
}
],
displayMeasurements: true,
locked: false,
ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE },
visibility: CONST.REGION_VISIBILITY.ALWAYS
},
{ create: true }
);
const scene = game.scenes.get(game.user.viewedScene);
const level = scene.levels.find(x => x.isView);
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
};
const placeRegion = data => {
canvas.regions.placeRegion(data, { create: true });
};
// Regions with effects must be placed by the GM
if (effects.length > 0 && !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]);

View file

@ -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',
@ -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, { action: 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) => {