import { emitAsGM, GMUpdateEvent } from '../../../systemRegistration/socket.mjs'; const fields = foundry.data.fields; export default class EffectsField extends fields.ArrayField { /** * Action Workflow order */ static order = 100; /** @inheritDoc */ constructor(options = {}, context = {}) { const element = new fields.SchemaField({ _id: new fields.DocumentIdField(), onSave: new fields.BooleanField({ initial: false }) }); super(element, options, context); } /** * Apply Effects Action Workflow part. * Must be called within Action context or similar. * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. * @param {object[]} [targets=null] Array of targets to override pre-selected ones. * @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example. */ static async execute(config, targets = null, force = false) { if (!config.hasEffect) return; let message = config.message ?? ui.chat.collection.get(config.parent?._id); if (!message && !config.skips.createMessage) { const roll = new CONFIG.Dice.daggerheart.DHRoll(''); roll._evaluated = true; message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config); } 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); // EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit)); } } /** * Apply Action Effects to a list of Targets * Must be called within Action context or similar. * @param {object[]} targets Array of formatted targets */ static async applyEffects(targets) { if (!this.effects?.length) return; let effects = this.effects.map(e => (this.item.applyEffects ?? this.item.effects).get(e._id)); const targettingRequired = effects.some(x => x.system.area?.type !== CONFIG.DH.EFFECTS.areaTypes.placed.id); if (targettingRequired && !targets?.length) return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));; for(const effect of effects.filter(effect => effect.system.area?.type === CONFIG.DH.EFFECTS.areaTypes.placed.id)) { await EffectsField.placeEffectRegion(effect); } const conditions = CONFIG.DH.GENERAL.conditions(); const messageTargets = []; targets.forEach(async baseToken => { if (this.hasSave && baseToken.saved.success === true) effects = this.effects.filter(e => e.onSave === true); if (!effects.length) return; const token = canvas.tokens.get(baseToken.id) ?? foundry.utils.fromUuidSync(baseToken.actorId).prototypeToken; if (!token) return; const messageToken = token.document ?? token; const conditionImmunities = messageToken.actor.system.rules.conditionImmunities ?? {}; messageTargets.push({ token: messageToken, conditionImmunities: Object.values(conditionImmunities).some(x => x) ? game.i18n.format('DAGGERHEART.UI.Chat.effectSummary.immunityTo', { immunities: Object.keys(conditionImmunities) .filter(x => conditionImmunities[x]) .map(x => game.i18n.localize(conditions[x].name)) .join(', ') }) : null }); effects.forEach(async effect => { if (!token.actor || !effect) return; if (effect.system.area?.type !== CONFIG.DH.EFFECTS.areaTypes.placed.id) await EffectsField.applyEffect(effect, token.actor); }); }); if (targettingRequired && messageTargets.length === 0) return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));; const summaryMessageSettings = game.settings.get( CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation ).summaryMessages; const appliedEffects = effects .filter(e => e.system.area?.type !== CONFIG.DH.EFFECTS.areaTypes.placed.id); if (!summaryMessageSettings.effects || !appliedEffects.length) return; const cls = getDocumentClass('ChatMessage'); const msg = { type: 'systemMessage', user: game.user.id, speaker: cls.getSpeaker(), title: game.i18n.localize('DAGGERHEART.UI.Chat.effectSummary.title'), content: await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/ui/chat/effectSummary.hbs', { effects: appliedEffects, targets: messageTargets } ) }; cls.create(msg); } /** * Apply an Effect to a target * @param {object} effect Effect object containing ActiveEffect UUID * @param {object} actor Actor Document */ static async applyEffect(effect, actor) { const effectData = foundry.utils.mergeObject({ ...(effect.toObject?.() ?? effect), disabled: false, transfer: false, origin: effect.uuid }); await ActiveEffect.implementation.create(effectData, { parent: actor }); } /** * */ static async placeEffectRegion(effect) { const { shape: type, size: range } = effect.system.area; const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range }); await canvas.regions.placeRegion( { name: effect.name, shapes: [shapeData], restriction: { enabled: false, type: 'move', priority: 0 }, behaviors: [{ name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'), type: 'applyActiveEffect', system: { effects: [effect.uuid] } }], displayMeasurements: true, locked: false, ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE }, visibility: CONST.REGION_VISIBILITY.ALWAYS }, { create: true } ); } /** * Return the automation setting for execute method for current user role * @returns {boolean} If execute should be triggered automatically */ static getAutomation() { return ( (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.players) ); } }