diff --git a/README.md b/README.md index 0c2dabc3..f59143fd 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart ## Contributing -Looking to contribute to the project? Look no further, check out our [contributing guide](contributing.md), and keep the [Code of Conduct](coc.md) in mind when working on things. +Looking to contribute to the project? Look no further, check out our [contributing guide](CONTRIBUTING.md), and keep the [Code of Conduct](coc.md) in mind when working on things. ## Disclaimer: diff --git a/daggerheart.mjs b/daggerheart.mjs index e25f1b09..363430be 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -342,7 +342,8 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => { const party = game.actors.get(data.partyId); if (!party) return; - const dialog = new game.system.api.applications.dialogs.TagTeamDialog(party); + const TagTeamDialog = game.system.api.applications.dialogs.TagTeamDialog; + const dialog = foundry.applications.instances.get(`TagTeamDialog-${party.id}`) ?? new TagTeamDialog(party); dialog.tabGroups.application = 'tagTeamRoll'; await dialog.render({ force: true }); } @@ -353,7 +354,8 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => { const party = game.actors.get(data.partyId); if (!party) return; - const dialog = new game.system.api.applications.dialogs.GroupRollDialog(party); + const GroupRollDialog = game.system.api.applications.dialogs.GroupRollDialog; + const dialog = foundry.applications.instances.get(`GroupRollDialog-${party.id}`) ?? new GroupRollDialog(party); dialog.tabGroups.application = 'groupRoll'; await dialog.render({ force: true }); } diff --git a/lang/en.json b/lang/en.json index 0d09acf9..cfc549c8 100755 --- a/lang/en.json +++ b/lang/en.json @@ -3221,7 +3221,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/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index 930ca1a1..b916a5de 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -22,9 +22,10 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap ); const orderedArmorSources = getArmorSources(actor).filter(s => !s.disabled); - const armor = orderedArmorSources.reduce((acc, { document }) => { + const armor = orderedArmorSources.reduce((acc, { name, document }) => { const { current, max } = document.type === 'armor' ? document.system.armor : document.system.armorData; acc.push({ + name, effect: document, marks: [...Array(max).keys()].reduce((acc, _, index) => { const spent = index < current; @@ -152,14 +153,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap const armorSources = []; for (const source of this.marks.armor) { - const parent = source.effect.origin - ? await foundry.utils.fromUuid(source.effect.origin) - : source.effect.parent; - - const useEffectName = parent.type === 'armor' || parent instanceof Actor; - const label = useEffectName ? source.effect.name : parent.name; armorSources.push({ - label: label, + label: source.name, uuid: source.effect.uuid, marks: source.marks }); diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs index 989e4625..367540bf 100644 --- a/module/applications/dialogs/downtime.mjs +++ b/module/applications/dialogs/downtime.mjs @@ -259,7 +259,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV const resetValue = increasing ? 0 : feature.system.resource.max - ? new Roll(Roll.replaceFormulaData(feature.system.resource.max, this.actor)).evaluateSync().total + ? new Roll( + Roll.replaceFormulaData(feature.system.resource.max, this.actor.getRollData()) + ).evaluateSync().total : 0; await feature.update({ 'system.resource.value': resetValue }); diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index df03a061..bd45fe91 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -1,12 +1,12 @@ 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; export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) { constructor(party) { - super(); + super({ id: `GroupRollDialog-${party.id}` }); this.party = party; this.partyMembers = party.system.partyMembers @@ -35,7 +35,6 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat static DEFAULT_OPTIONS = { tag: 'form', - id: 'GroupRollDialog', classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'], position: { width: 390, height: 'auto' }, window: { @@ -243,7 +242,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat }); }; - await emitAsGM( + await emitGMUpdate( GMUpdateEvent.UpdateDocument, gmUpdate, update, diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 026c4bc0..e06cbe48 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -1,13 +1,13 @@ 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; export default class TagTeamDialog extends HandlebarsApplicationMixin(ApplicationV2) { constructor(party) { - super(); + super({ id: `TagTeamDialog-${party.id}` }); this.party = party; this.partyMembers = party.system.partyMembers @@ -36,7 +36,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio static DEFAULT_OPTIONS = { tag: 'form', - id: 'TagTeamDialog', classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'], position: { width: 550, height: 'auto' }, actions: { @@ -60,13 +59,17 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio id: 'initialization', template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/initialization.hbs' }, + tagTeamRoll: { + id: 'tagTeamRoll', + template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs' + }, rollSelection: { id: 'rollSelection', template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/rollSelection.hbs' }, - tagTeamRoll: { - id: 'tagTeamRoll', - template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs' + result: { + id: 'result', + template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/result.hbs' } }; @@ -97,36 +100,15 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } _configureRenderParts(options) { - const { initialization, rollSelection, tagTeamRoll } = super._configureRenderParts(options); - const augmentedParts = { initialization }; + const parts = super._configureRenderParts(options); for (const memberKey of Object.keys(this.party.system.tagTeam.members)) { - augmentedParts[memberKey] = { + parts[memberKey] = { id: memberKey, template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamMember.hbs' }; } - augmentedParts.rollSelection = rollSelection; - augmentedParts.tagTeamRoll = tagTeamRoll; - return augmentedParts; - } - - /**@inheritdoc */ - async _onRender(context, options) { - await super._onRender(context, options); - - // if (this.element.querySelector('.roll-selection')) { - // for (const element of this.element.querySelectorAll('.team-member-container')) { - // element.classList.add('select-padding'); - // } - // } - - if (this.element.querySelector('.team-container')) return; - const initializationPart = this.element.querySelector('.initialization-container'); - initializationPart.insertAdjacentHTML('afterend', '
'); - const teamContainer = this.element.querySelector('.team-container'); - for (const memberContainer of this.element.querySelectorAll('.team-member-container')) - teamContainer.appendChild(memberContainer); + return parts; } async _prepareContext(_options) { @@ -167,6 +149,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio partContext.initiatorDisabled = !selectedMembers.length; partContext.openForAllPlayers = this.openForAllPlayers; + break; + case 'tagTeamRoll': + partContext.memberKeys = Object.keys(this.party.system.tagTeam.members); break; case 'rollSelection': partContext.members = Object.keys(this.party.system.tagTeam.members).reduce((acc, key) => { @@ -175,7 +160,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio return acc; }, {}); break; - case 'tagTeamRoll': + case 'result': const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected); const critSelected = !selectedRoll ? undefined @@ -243,7 +228,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } getUpdatingParts(target) { - const { initialization, rollSelection, tagTeamRoll } = this.constructor.PARTS; + const { initialization, rollSelection, result } = this.constructor.PARTS; const isInitialization = this.tabGroups.application === initialization.id; const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey; @@ -251,7 +236,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ...(isInitialization ? [initialization.id] : []), ...(updatingMember ? [updatingMember] : []), ...(!isInitialization ? [rollSelection.id] : []), - ...(!isInitialization ? [tagTeamRoll.id] : []) + ...(!isInitialization ? [result.id] : []) ]; } @@ -274,7 +259,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio }); }; - await emitAsGM( + await emitGMUpdate( GMUpdateEvent.UpdateDocument, gmUpdate, update, diff --git a/module/applications/scene/sceneConfigSettings.mjs b/module/applications/scene/sceneConfigSettings.mjs index ce312a45..0a999506 100644 --- a/module/applications/scene/sceneConfigSettings.mjs +++ b/module/applications/scene/sceneConfigSettings.mjs @@ -120,12 +120,6 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S foundry.utils.fromUuidSync(x) ); - for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) { - if (!submitData.flags.daggerheart.sceneEnvironments[key]) { - submitData.flags.daggerheart.sceneEnvironments[key] = _del; - } - } - super._processSubmitData(event, form, submitData, options); } } diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index 6593f23d..57405675 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -110,6 +110,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings { } async _onDrop(event) { + event.stopPropagation(); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const item = await fromUuid(data.uuid); diff --git a/module/applications/sheets-configs/environment-settings.mjs b/module/applications/sheets-configs/environment-settings.mjs index bc0efef2..6d74f9c6 100644 --- a/module/applications/sheets-configs/environment-settings.mjs +++ b/module/applications/sheets-configs/environment-settings.mjs @@ -121,6 +121,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { } async _onDrop(event) { + event.stopPropagation(); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const item = await fromUuid(data.uuid); if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return; @@ -138,8 +139,4 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { this.render(); } } - - async _onDropItem(event, item) { - console.log(item); - } } diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index e941931a..36477821 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -531,7 +531,7 @@ export default function DHApplicationMixin(Base) { visible: element => { const target = element.closest('[data-item-uuid]'); const doc = getDocFromElementSync(target); - return doc?.isOwner && target.dataset.itemType !== 'beastform'; + return doc?.isOwner !== false && target.dataset.itemType !== 'beastform'; }, callback: async (target, event) => { const doc = await getDocFromElement(target); diff --git a/module/applications/ui/countdownEdit.mjs b/module/applications/ui/countdownEdit.mjs index 8bb9fc1d..b418107c 100644 --- a/module/applications/ui/countdownEdit.mjs +++ b/module/applications/ui/countdownEdit.mjs @@ -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 }); diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 79a59a07..052564cc 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -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 }); } diff --git a/module/applications/ui/fearTracker.mjs b/module/applications/ui/fearTracker.mjs index 4e5e1132..8c247f79 100644 --- a/module/applications/ui/fearTracker.mjs +++ b/module/applications/ui/fearTracker.mjs @@ -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 diff --git a/module/applications/ui/sceneNavigation.mjs b/module/applications/ui/sceneNavigation.mjs index a0005fc7..982063e7 100644 --- a/module/applications/ui/sceneNavigation.mjs +++ b/module/applications/ui/sceneNavigation.mjs @@ -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 }, 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/data/actor/character.mjs b/module/data/actor/character.mjs index d42f2f64..a1cd13e8 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -836,10 +836,10 @@ export default class DhCharacter extends DhCreature { /* Scars can alter the amount of current hope */ if (changes.system?.scars) { - const diff = this.system.scars - changes.system.scars; - const newHopeMax = this.system.resources.hope.max + diff; - const newHopeValue = Math.min(newHopeMax, this.system.resources.hope.value); - if (newHopeValue != this.system.resources.hope.value) { + const diff = this.scars - changes.system.scars; + const newHopeMax = this.resources.hope.max + diff; + const newHopeValue = Math.min(newHopeMax, this.resources.hope.value); + if (newHopeValue != this.resources.hope.value) { if (!changes.system.resources.hope) changes.system.resources.hope = { value: 0 }; changes.system.resources.hope = { diff --git a/module/data/fields/action/countdownField.mjs b/module/data/fields/action/countdownField.mjs index 719ca749..990f8ef1 100644 --- a/module/data/fields/action/countdownField.mjs +++ b/module/data/fields/action/countdownField.mjs @@ -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); diff --git a/module/data/fields/action/effectsField.mjs b/module/data/fields/action/effectsField.mjs index 9a4ffc31..1053e51d 100644 --- a/module/data/fields/action/effectsField.mjs +++ b/module/data/fields/action/effectsField.mjs @@ -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)); } } diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 209631f6..83dbbaf2 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -257,7 +257,7 @@ export default class DHRoll extends Roll { if (!roll.terms[i].isDeterministic) continue; const termTotal = roll.terms[i].total; if (typeof termTotal === 'number') { - const multiplier = roll.terms[i - 1]?.operator === ' - ' ? -1 : 1; + const multiplier = roll.terms[i - 1]?.operator === '-' ? -1 : 1; modifierTotal += multiplier * termTotal; } } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 08463818..f9239a90 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -171,6 +171,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { /** Recursively finds the first parent document of the given object */ static #resolveParentDocument(model, documentClass) { + if (!model) return null; return model instanceof documentClass ? model : model.parent diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index eb57a186..5df87b6c 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -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, diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 2e20fb87..78bab016 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -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]); diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 1650b505..faa046ff 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -757,9 +757,12 @@ export function getArmorSources(actor) { // Get the origin item. Since the actor is already loaded, it should already be cached // Consider the relative function versions if this causes an issue const origin = doc.origin ? foundry.utils.fromUuidSync(doc.origin) : doc; + const useParentName = doc.parent && !(doc.parent instanceof Actor); + const name = doc.origin || !useParentName ? doc.name : doc.parent.name; + return { origin, - name: origin.name, + name, document: doc, data: doc.system.armor ?? doc.system.armorData, disabled: !!doc.disabled || !!doc.isSuppressed diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index 8fed346d..de9bf00c 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', @@ -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) => { diff --git a/src/packs/adversaries/adversary_Dire_Bat_tBWHW00epmMnkawe.json b/src/packs/adversaries/adversary_Dire_Bat_tBWHW00epmMnkawe.json index c2064395..a1107f7c 100644 --- a/src/packs/adversaries/adversary_Dire_Bat_tBWHW00epmMnkawe.json +++ b/src/packs/adversaries/adversary_Dire_Bat_tBWHW00epmMnkawe.json @@ -40,7 +40,8 @@ "experiences": { "ti3Z1mq2M92KK4GJ": { "name": "Bloodthirsty", - "description": "" + "description": "", + "value": 3 } }, "bonuses": { @@ -242,27 +243,24 @@ "type": "withinRange", "target": "hostile", "range": "melee" - } + }, + "changes": [ + { + "key": "system.difficulty", + "value": 3, + "priority": null, + "type": "add" + } + ] }, "_id": "qZfNiqw1iAIxeuYg", "img": "icons/commodities/biological/wing-lizard-brown.webp", - "changes": [ - { - "key": "system.difficulty", - "mode": 2, - "value": "3", - "priority": null - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "While flying, the Bat gains a +3 bonus to their Difficulty.
", "origin": null, @@ -274,6 +272,9 @@ "_stats": { "compendiumSource": null }, + "start": null, + "showIcon": 1, + "folder": null, "_key": "!actors.items.effects!tBWHW00epmMnkawe.gx22MpD8fWoi8klZ.qZfNiqw1iAIxeuYg" } ], diff --git a/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json b/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json index e59d2683..72ad8ae2 100644 --- a/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json +++ b/src/packs/adversaries/adversary_Greater_Earth_Elemental_dsfB3YhoL5SudvS2.json @@ -249,7 +249,7 @@ "name": "Crushing Blows", "type": "feature", "system": { - "description": "When the @Lookup[@name] makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP.
", + "description": "When the @Lookup[@name] makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP.
", "resource": null, "actions": { "0sXciTiPc30v8czv": { diff --git a/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json b/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json index 1615dec8..183719f2 100644 --- a/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json +++ b/src/packs/adversaries/adversary_Huge_Green_Ooze_6hbqmxDXFOzZJDk4.json @@ -138,12 +138,9 @@ "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "anchorX": 0.5, "anchorY": 0.5, - "offsetX": 0, - "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, - "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -194,7 +191,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": [], + "detectionModes": {}, "occludable": { "radius": 0 }, @@ -220,7 +217,8 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false + "prependAdjective": false, + "depth": 1 }, "items": [ { @@ -257,7 +255,7 @@ "name": "Acidic Form", "type": "feature", "system": { - "description": "When the @Lookup[@name] makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP.
", + "description": "When the @Lookup[@name] makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP.
", "resource": null, "actions": { "gtT2oHSyZg9OHHJD": { diff --git a/system.json b/system.json index 9b8a8403..ac581343 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.2.1", + "version": "2.2.3", "compatibility": { "minimum": "14.359", "verified": "14.360", @@ -10,7 +10,7 @@ }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.1/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.3/system.zip", "authors": [ { "name": "WBHarry" diff --git a/templates/dialogs/tagTeamDialog/result.hbs b/templates/dialogs/tagTeamDialog/result.hbs new file mode 100644 index 00000000..79b5138e --- /dev/null +++ b/templates/dialogs/tagTeamDialog/result.hbs @@ -0,0 +1,38 @@ +