diff --git a/daggerheart.mjs b/daggerheart.mjs index bf8a9f63..56ad3e3d 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -19,7 +19,6 @@ import { } from './module/systemRegistration/_module.mjs'; import { placeables } from './module/canvas/_module.mjs'; import { registerRollDiceHooks } from './module/dice/dhRoll.mjs'; -import { registerDHActorHooks } from './module/documents/actor.mjs'; import './node_modules/@yaireo/tagify/dist/tagify.css'; Hooks.once('init', () => { @@ -169,7 +168,7 @@ Hooks.on('ready', () => { registerCountdownHooks(); socketRegistration.registerSocketHooks(); registerRollDiceHooks(); - registerDHActorHooks(); + socketRegistration.registerUserQueries(); }); Hooks.once('dicesoniceready', () => {}); diff --git a/lang/en.json b/lang/en.json index c02898f0..40317f8c 100755 --- a/lang/en.json +++ b/lang/en.json @@ -453,6 +453,9 @@ "title": "Ownership Selection - {name}", "default": "Default Ownership" }, + "ReactionRoll": { + "title": "Reaction Roll: {trait}" + }, "ResourceDice": { "title": "{name} Resource", "rerollDice": "Reroll Dice" diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index 3e3bde44..9049522d 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -1,6 +1,6 @@ import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs'; -const { DialogV2, ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; +const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; export default class DamageReductionDialog extends HandlebarsApplicationMixin(ApplicationV2) { constructor(resolve, reject, actor, damage, damageType) { @@ -53,10 +53,6 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap ); } - get title() { - return game.i18n.localize('DAGGERHEART.APPLICATIONS.DamageReduction.title'); - } - static DEFAULT_OPTIONS = { tag: 'form', classes: ['daggerheart', 'views', 'damage-reduction'], diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index e0f990ba..5e507a3b 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -1,3 +1,5 @@ +import { emitAsGM, GMUpdateEvent } from "../../systemRegistration/socket.mjs"; + export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog { constructor(options) { super(options); @@ -98,17 +100,41 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo if (message.system.source.item && message.system.source.action) { const action = this.getAction(actor, message.system.source.item, message.system.source.action); if (!action || !action?.hasSave) return; - action.rollSave(token, event, message); + action.rollSave(token.actor, event, message).then(result => emitAsGM( + GMUpdateEvent.UpdateSaveMessage, + action.updateSaveMessage.bind(action, result, message, token.id), + { + action: action.uuid, + message: message._id, + token: token.id, + result + } + )); } } - onRollAllSave(event, _message) { + async onRollAllSave(event, message) { event.stopPropagation(); + if(!game.user.isGM) return; const targets = event.target.parentElement.querySelectorAll( '.target-section > [data-token] .target-save-container' ); - targets.forEach(el => { - el.dispatchEvent(new PointerEvent('click', { shiftKey: true })); + const actor = await this.getActor(message.system.source.actor), + action = this.getAction(actor, message.system.source.item, message.system.source.action); + targets.forEach(async el => { + const tokenId = el.closest('[data-token]')?.dataset.token, + token = game.canvas.tokens.get(tokenId); + if(!token.actor) return; + if(game.user === token.actor.owner) + el.dispatchEvent(new PointerEvent('click', { shiftKey: true })); + else { + token.actor.owner.query('reactionRoll', { + actionId: action.uuid, + actorId: token.actor.uuid, + event, + message + }).then(result => action.updateSaveMessage(result, message, token.id)); + } }); } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index f3fdb3d6..8f0e0682 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -299,9 +299,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel /* EFFECTS */ /* SAVE */ - async rollSave(target, event, message) { - if (!target?.actor) return; - return target.actor + async rollSave(actor, event, message) { + if (!actor) return; + return actor .diceRoll({ event, title: 'Roll Save', @@ -310,16 +310,28 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel difficulty: this.save.difficulty ?? this.actor?.baseSaveDifficulty, type: 'reaction' }, - data: target.actor.getRollData() - }) - .then(async result => { - if (result) - this.updateChatMessage(message, target.id, { - result: result.roll.total, - success: result.roll.success - }); + data: actor.getRollData() }); } + + updateSaveMessage(result, message, targetId) { + const updateMsg = this.updateChatMessage.bind(this, message, targetId, { + result: result.roll.total, + success: result.roll.success + }); + if (game.modules.get('dice-so-nice')?.active) + game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(() => updateMsg()); + else updateMsg(); + } + + static rollSaveQuery({ actionId, actorId, event, message }) { + return new Promise(async (resolve, reject) => { + const actor = await fromUuid(actorId), + action = await fromUuid(actionId); + if (!actor || !actor?.isOwner) reject(); + action.rollSave(actor, event, message).then(result => resolve(result)); + }); + } /* SAVE */ async updateChatMessage(message, targetId, changes, chain = true) { @@ -333,7 +345,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel if (chain) { if (message.system.source.message) this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false); - const relatedChatMessages = ui.chat.collection.filter(c => c.system.source.message === message._id); + const relatedChatMessages = ui.chat.collection.filter(c => c.system.source?.message === message._id); relatedChatMessages.forEach(c => { this.updateChatMessage(c, targetId, changes, false); }); diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index 1f4060b0..f0db33e9 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -5,12 +5,14 @@ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) => resistance: new foundry.data.fields.BooleanField({ initial: false, label: `${resistanceLabel}.label`, - hint: `${resistanceLabel}.hint` + hint: `${resistanceLabel}.hint`, + isAttributeChoice: true }), immunity: new foundry.data.fields.BooleanField({ initial: false, label: `${immunityLabel}.label`, - hint: `${immunityLabel}.hint` + hint: `${immunityLabel}.hint`, + isAttributeChoice: true }), reduction: new foundry.data.fields.NumberField({ integer: true, diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 43e275d4..d74ae410 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -12,7 +12,7 @@ export default class DamageRoll extends DHRoll { static async buildEvaluate(roll, config = {}, message = {}) { if (config.evaluate !== false) { - if (config.dialog.configure === false) roll.constructFormula(config); + // if (config.dialog.configure === false) roll.constructFormula(config); for (const roll of config.roll) await roll.roll.evaluate(); } roll._evaluated = true; diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index c60a0b90..ec464ddb 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -1,5 +1,4 @@ import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs'; -import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs'; import { LevelOptionType } from '../data/levelTier.mjs'; import DHFeature from '../data/item/feature.mjs'; import { damageKeyToNumber } from '../helpers/utils.mjs'; @@ -483,10 +482,14 @@ export default class DhpActor extends Actor { this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes) ) { const armorStackResult = await this.owner.query('armorStack', { - actorId: this.uuid, - damage: hpDamage.value, - type: [...hpDamage.damageTypes] - }); + actorId: this.uuid, + damage: hpDamage.value, + type: [...hpDamage.damageTypes] + }, + { + timeout: 30000 + } + ); if (armorStackResult) { const { modifiedDamage, armorSpent, stressSpent } = armorStackResult; updates.find(u => u.key === 'hitPoints').value = modifiedDamage; @@ -638,7 +641,3 @@ export default class DhpActor extends Actor { }); } } - -export const registerDHActorHooks = () => { - CONFIG.queries.armorStack = DamageReductionDialog.armorStackQuery; -}; diff --git a/module/documents/token.mjs b/module/documents/token.mjs index a8105eb2..b6c47450 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -52,6 +52,7 @@ export default class DHToken extends TokenDocument { for (const [name, field] of Object.entries(schema.fields)) { const p = _path.concat([name]); if (field instanceof foundry.data.fields.NumberField) attributes.value.push(p); + if (field instanceof foundry.data.fields.BooleanField && field.options.isAttributeChoice) attributes.value.push(p); if (field instanceof foundry.data.fields.StringField) attributes.value.push(p); if (field instanceof foundry.data.fields.ArrayField) attributes.value.push(p); const isSchema = field instanceof foundry.data.fields.SchemaField; diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index 0037d99d..e97fe5b0 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -1,3 +1,5 @@ +import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs'; + export function handleSocketEvent({ action = null, data = {} } = {}) { switch (action) { case socketEvent.GMUpdate: @@ -21,7 +23,8 @@ export const socketEvent = { export const GMUpdateEvent = { UpdateDocument: 'DhGMUpdateDocument', UpdateSetting: 'DhGMUpdateSetting', - UpdateFear: 'DhGMUpdateFear' + UpdateFear: 'DhGMUpdateFear', + UpdateSaveMessage: 'DhGMUpdateSaveMessage' }; export const RefreshType = { @@ -53,8 +56,12 @@ export const registerSocketHooks = () => { ) ) ); - /* Hooks.callAll(socketEvent.DhpFearUpdate); - await game.socket.emit(`system.${CONFIG.DH.id}`, { action: socketEvent.DhpFearUpdate }); */ + break; + case GMUpdateEvent.UpdateSaveMessage: + const action = await fromUuid(data.update.action), + message = game.messages.get(data.update.message); + if(!action || !message) return; + action.updateSaveMessage(data.update.result, message, data.update.token); break; } @@ -69,6 +76,11 @@ export const registerSocketHooks = () => { }); }; +export const registerUserQueries = () => { + CONFIG.queries.armorStack = DamageReductionDialog.armorStackQuery; + CONFIG.queries.reactionRoll = game.system.api.models.actions.actionsTypes.base.rollSaveQuery; +} + export const emitAsGM = async (eventName, callback, update, uuid = null) => { if (!game.user.isGM) { return await game.socket.emit(`system.${CONFIG.DH.id}`, { diff --git a/templates/dialogs/reactionRoll.hbs b/templates/dialogs/reactionRoll.hbs new file mode 100644 index 00000000..98c94f16 --- /dev/null +++ b/templates/dialogs/reactionRoll.hbs @@ -0,0 +1,3 @@ +
+ Reaction Roll +
\ No newline at end of file