diff --git a/lang/en.json b/lang/en.json index ffa54cff..f40e82a9 100755 --- a/lang/en.json +++ b/lang/en.json @@ -71,7 +71,9 @@ "name": "Summon", "tooltip": "Create tokens in the scene.", "error": "You do not have permission to summon tokens or there is no active scene.", - "invalidDrop": "You can only drop Actor entities to summon." + "invalidDrop": "You can only drop Actor entities to summon.", + "chatMessageTitle": "Test2", + "chatMessageHeaderTitle": "Summoning" } }, "Config": { @@ -2180,6 +2182,10 @@ "stress": "Stress", "subclasses": "Subclasses", "success": "Success", + "summon": { + "single": "Summon", + "plural": "Summons" + }, "take": "Take", "Target": { "single": "Target", diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index d30d9c08..7224bd72 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -111,7 +111,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) context.source = this.action.toObject(true); context.summons = []; - for (const summon of context.source.summon) { + for (const summon of context.source.summon ?? []) { const actor = await foundry.utils.fromUuid(summon.actorUUID); context.summons.push({ actor, count: summon.count }); } @@ -261,7 +261,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) const wrapper = event.target.closest('.summon-count-wrapper'); const index = wrapper.dataset.index; const data = this.action.toObject(); - data.summon[index].count = Number.parseInt(event.target.value); + data.summon[index].count = event.target.value; this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index a9521b4a..ec4e6422 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -163,15 +163,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel */ getRollData(data = {}) { const actorData = this.actor ? this.actor.getRollData(false) : {}; + actorData.result = data.roll?.total ?? 1; + actorData.scale = data.costs?.length // Right now only return the first scalable cost. + ? (data.costs.find(c => c.scalable)?.total ?? 1) + : 1; + actorData.roll = {}; - return { - ...actorData, - result: data.roll?.total ?? 1, - scale: data.costs?.length // Right now only return the first scalable cost. - ? (data.costs.find(c => c.scalable)?.total ?? 1) - : 1, - roll: {} - }; + return actorData; } /** diff --git a/module/data/fields/action/summonField.mjs b/module/data/fields/action/summonField.mjs index da884c42..6ecb7296 100644 --- a/module/data/fields/action/summonField.mjs +++ b/module/data/fields/action/summonField.mjs @@ -1,3 +1,5 @@ +import FormulaField from '../formulaField.mjs'; + const fields = foundry.data.fields; export default class DHSummonField extends fields.ArrayField { @@ -12,11 +14,9 @@ export default class DHSummonField extends fields.ArrayField { type: 'Actor', required: true }), - count: new fields.NumberField({ + count: new FormulaField({ required: true, - default: 1, - min: 1, - integer: true + default: '1' }) }); super(summonFields, options, context); @@ -33,38 +33,57 @@ export default class DHSummonField extends fields.ArrayField { return; } - this.actor.sheet?.minimize(); + const rolls = []; const summonData = []; for (const summon of this.summon) { - /* Possibly check for any available instances in the world with prepared images */ - let actor = await foundry.utils.fromUuid(summon.actorUUID); - - /* Check for any available instances of the actor present in the world if we're missing artwork in the compendium */ - const dataType = game.system.api.data.actors[`Dh${actor.type.capitalize()}`]; - if (actor.inCompendium && dataType && actor.img === dataType.DEFAULT_ICON) { - const worldActorCopy = game.actors.find(x => x.name === actor.name); - actor = worldActorCopy ?? actor; + let count = summon.count; + const roll = new Roll(summon.count); + if (!roll.isDeterministic) { + await roll.evaluate(); + if (game.modules.get('dice-so-nice')?.active) rolls.push(roll); + count = roll.total; } - summonData.push({ actor, count: summon.count }); + const actor = DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); + /* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */ + summon.rolledCount = count; + summon.actor = actor.toObject(); + + summonData.push({ actor, count: count }); } - const handleSummon = async summonIndex => { - const summon = summonData[summonIndex]; - const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, { - name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}` - }); - if (!result) return this.actor.sheet?.maximize(); + if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true))); - summon.count--; - if (summon.count === 0) { - summonIndex++; - if (summonIndex === summonData.length) return this.actor.sheet?.maximize(); - } + this.actor.sheet?.minimize(); + DHSummonField.handleSummon(summonData, this.actor); + } - handleSummon(summonIndex); - }; + /* Check for any available instances of the actor present in the world if we're missing artwork in the compendium */ + static getWorldActor(baseActor) { + const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`]; + if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) { + const worldActorCopy = game.actors.find(x => x.name === baseActor.name); + return worldActorCopy ?? baseActor; + } - handleSummon(0); + return baseActor; + } + + static async handleSummon(summonData, actionActor, summonIndex = 0) { + const summon = summonData[summonIndex]; + const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, { + name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}` + }); + + if (!result) return actionActor.sheet?.maximize(); + summon.actor = result.actor; + + summon.count--; + if (summon.count === 0) { + summonIndex++; + if (summonIndex === summonData.length) return actionActor.sheet?.maximize(); + } + + DHSummonField.handleSummon(summonData, actionActor, summonIndex); } } diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs index 6257da38..3578c760 100644 --- a/module/data/fields/actionField.mjs +++ b/module/data/fields/actionField.mjs @@ -267,7 +267,8 @@ export function ActionMixin(Base) { action: { name: this.name, img: this.baseAction ? this.parent.parent.img : this.img, - tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'] + tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'], + summon: this.summon }, itemOrigin: this.item, description: this.description || (this.item instanceof Item ? this.item.system.description : '') diff --git a/module/documents/tokenManager.mjs b/module/documents/tokenManager.mjs index 0a878887..be5467da 100644 --- a/module/documents/tokenManager.mjs +++ b/module/documents/tokenManager.mjs @@ -94,11 +94,11 @@ export default class DhTokenManager { ? await game.system.api.documents.DhpActor.create(this.#actor.toObject()) : this.#actor; const tokenData = await actor.getTokenDocument(); - const token = await canvas.scene.createEmbeddedDocuments('Token', [ + const result = await canvas.scene.createEmbeddedDocuments('Token', [ { ...tokenData, x: this.#activePreview.document.x, y: this.#activePreview.document.y } ]); this.#activePreview = undefined; - if (this.#resolve) this.#resolve(token); + if (this.#resolve && result.length) this.#resolve(result[0]); } } diff --git a/styles/less/ui/chat/action.less b/styles/less/ui/chat/action.less index 817b0acd..bdc6a0ed 100644 --- a/styles/less/ui/chat/action.less +++ b/styles/less/ui/chat/action.less @@ -98,6 +98,51 @@ .description { padding: 8px; + + .summons-header { + font-size: var(--font-size-14); + text-align: center; + + span::before, + span::after { + color: @dark-blue; + } + &:before { + background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, @dark-blue 100%); + } + + &:after { + background: linear-gradient(90deg, @dark-blue 0%, rgba(0, 0, 0, 0) 100%); + } + } + + .summons-container { + display: flex; + flex-direction: column; + gap: 4px; + + .summon-container { + display: flex; + align-items: center; + justify-content: space-between; + + .summon-label-container { + flex: 1; + display: flex; + align-items: center; + gap: 4px; + + img { + height: 32px; + } + + label { + display: flex; + flex-wrap: wrap; + } + } + } + } } .ability-card-footer { diff --git a/templates/ui/chat/action.hbs b/templates/ui/chat/action.hbs index 6b505164..65bb0762 100644 --- a/templates/ui/chat/action.hbs +++ b/templates/ui/chat/action.hbs @@ -8,6 +8,22 @@ -