From be61673e874c4dd6a2471820e8b66f5f36e95d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mang=C3=B3?= Date: Sun, 17 Nov 2024 14:37:13 +0100 Subject: [PATCH 1/3] Properly enrich target tray --- src/lang/en.json | 3 + src/style/chat/module.scss | 100 ++++++++++++++++++++++- src/system/dice/d20-roll.ts | 8 ++ src/system/documents/actor.ts | 2 + src/system/documents/chat-message.ts | 52 +++++++++++- src/system/documents/item.ts | 7 +- src/system/utils/generic.ts | 47 +++++++++++ src/system/utils/templates.ts | 2 + src/templates/chat/card-tray-targets.hbs | 26 ++++++ 9 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 src/templates/chat/card-tray-targets.hbs diff --git a/src/lang/en.json b/src/lang/en.json index 947f4672..5faf15a3 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -862,6 +862,9 @@ "RollAdvantage": "Click to roll with advantage.", "RollDisadvantage": "Click to roll with disadvantage." }, + "Trays": { + "Targets": "Targets" + }, "InjuryRoll": "Injury Roll", "InjuryDuration": { "Dead": "{actor} has died.", diff --git a/src/style/chat/module.scss b/src/style/chat/module.scss index 2b63bf1d..2a009cd4 100644 --- a/src/style/chat/module.scss +++ b/src/style/chat/module.scss @@ -477,14 +477,110 @@ } } + .chat-card-tray { + & > label { + display: flex; + justify-content: center; + align-items: center; + gap: 0.25rem; + font-size: var(--font-size-11); + font-family: var(--plotweaver-font-normal); + font-weight: bold; + text-transform: uppercase; + + & > span { + flex: none; + } + + & > i:first-of-type { + color: var(--plotweaver-color-light-2); + } + } + + & > label::before, + & > label::after { + content: ""; + flex-basis: 50%; + border-top: 1px dotted var(--plotweaver-color-dark-6); + align-self: center; + } + + .target-headers { + color: var(--plotweaver-color-light-2); + font-size: var(--font-size-10); + font-family: var(--plotweaver-font-condensed); + font-weight: 600; + text-transform: uppercase; + display: flex; + align-items: center; + justify-content: flex-end; + margin-top: 0.25rem; + + & > span { + width: 15%; + text-align: center; + } + } + + .target-list { + display: flex; + flex-direction: column; + gap: 0.25rem; + list-style: none; + padding: 0; + margin: 0.25rem 0; + + .target { + display: flex; + align-items: center; + cursor: pointer; + font-size: var(--font-size-13); + font-family: var(--plotweaver-font-normal); + font-weight: bold; + + .name { + color: var(--plotweaver-color-dark-1); + width: 55%; + } + + .result { + color: var(--plotweaver-color-dark-4); + width: 15%; + text-align: center; + + .success { + color: var(--plotweaver-color-health-front) + } + + .failure { + color: var(--plotweaver-color-complication) + } + } + } + } + } + .collapsible { cursor: pointer; .collapsible-content { display: grid; grid-template-rows: 0fr; - transition: grid-template-rows 250ms ease; - } + transition: grid-template-rows 250ms ease; + + & > .wrapper { + overflow: hidden; + } + } + + .fa-caret-down { + transform: rotate(-90deg); + transition: all 250ms ease; + } + + &.expanded .fa-caret-down { + transform: rotate(0deg); + } &.expanded .collapsible-content { grid-template-rows: 1fr; diff --git a/src/system/dice/d20-roll.ts b/src/system/dice/d20-roll.ts index 42ff1a66..d9a1d6ce 100644 --- a/src/system/dice/d20-roll.ts +++ b/src/system/dice/d20-roll.ts @@ -400,6 +400,14 @@ export class D20Roll extends foundry.dice.Roll { }); } + /** + * Recalculates the roll total from the current (potentially modified) terms. + * @returns {number} The new total of the roll. + */ + public resetTotal(): number { + return (this._total = this._evaluateTotal()); + } + /* --- Internal Functions --- */ private configureModifiers() { diff --git a/src/system/documents/actor.ts b/src/system/documents/actor.ts index 0b81cf6d..09869566 100644 --- a/src/system/documents/actor.ts +++ b/src/system/documents/actor.ts @@ -37,6 +37,7 @@ import { d20Roll, D20Roll, D20RollData, DamageRoll } from '@system/dice'; // Dialogs import { ShortRestDialog } from '@system/applications/actor/dialogs/short-rest'; import { MESSAGE_TYPES } from './chat-message'; +import { getTargetDescriptors } from '../utils/generic'; export type CharacterActor = CosmereActor; export type AdversaryActor = CosmereActor; @@ -713,6 +714,7 @@ export class CosmereActor< rollData.messageData.flags[SYSTEM_ID] = { message: { type: MESSAGE_TYPES.SKILL, + targets: getTargetDescriptors(), }, }; diff --git a/src/system/documents/chat-message.ts b/src/system/documents/chat-message.ts index e230bd3d..d0369b14 100644 --- a/src/system/documents/chat-message.ts +++ b/src/system/documents/chat-message.ts @@ -7,7 +7,11 @@ import { renderSystemTemplate, TEMPLATES } from '../utils/templates'; import { SYSTEM_ID } from '../constants'; import { AdvantageMode } from '../types/roll'; import { getSystemSetting, SETTINGS } from '../settings'; -import { getApplyTargets, getConstantFromRoll } from '../utils/generic'; +import { + getApplyTargets, + getConstantFromRoll, + TargetDescriptor, +} from '../utils/generic'; export const MESSAGE_TYPES = { SKILL: 'skill', @@ -117,6 +121,7 @@ export class CosmereChatMessage extends ChatMessage { await this.enrichSkillTest(content); await this.enrichDamage(content); await this.enrichInjury(content); + await this.enrichTestTargets(content); // Replace content html.find('.message-content').replaceWith(content); @@ -180,6 +185,48 @@ export class CosmereChatMessage extends ChatMessage { html.find('.chat-card').append(section); } + protected async enrichTestTargets(html: JQuery) { + if (!this.hasSkillTest) return; + + const targets = this.getFlag( + SYSTEM_ID, + 'message.targets', + ) as TargetDescriptor[]; + if (!targets || targets.length === 0) return; + + const d20Roll = this.d20Rolls[0]; + + const success = ''; + const failure = ''; + + const targetData = []; + for (const target of targets) { + targetData.push({ + name: target.name, + phyDef: target.def.phy, + phyIcon: + (d20Roll.total ?? 0) >= target.def.phy ? success : failure, + cogDef: target.def.cog, + cogIcon: + (d20Roll.total ?? 0) >= target.def.cog ? success : failure, + spiDef: target.def.spi, + spiIcon: + (d20Roll.total ?? 0) >= target.def.spi ? success : failure, + }); + } + + const trayHTML = await renderSystemTemplate( + TEMPLATES.CHAT_CARD_TRAY_TARGETS, + { + targets: targetData, + }, + ); + + const tray = $(trayHTML as unknown as HTMLElement); + + html.find('.chat-card').append(tray); + } + protected async enrichDamage(html: JQuery) { if (!this.hasDamage) return; @@ -509,6 +556,9 @@ export class CosmereChatMessage extends ChatMessage { ? AdvantageMode.Disadvantage : AdvantageMode.None; + roll.resetFormula(); + roll.resetTotal(); + void this.update({ rolls: this.rolls }); } } diff --git a/src/system/documents/item.ts b/src/system/documents/item.ts index 0ba8d320..8283d195 100644 --- a/src/system/documents/item.ts +++ b/src/system/documents/item.ts @@ -63,7 +63,11 @@ import { } from '@system/dice'; import { AdvantageMode } from '@system/types/roll'; import { RollMode } from '@system/dice/types'; -import { determineConfigurationMode, hasKey } from '../utils/generic'; +import { + determineConfigurationMode, + getTargetDescriptors, + hasKey, +} from '../utils/generic'; import { MESSAGE_TYPES } from './chat-message'; import { renderSystemTemplate, TEMPLATES } from '../utils/templates'; @@ -855,6 +859,7 @@ export class CosmereItem< message: { type: MESSAGE_TYPES.ACTION, description: await this.getDescriptionHTML(), + targets: getTargetDescriptors(), }, }; diff --git a/src/system/utils/generic.ts b/src/system/utils/generic.ts index dda9c37d..751862ae 100644 --- a/src/system/utils/generic.ts +++ b/src/system/utils/generic.ts @@ -1,3 +1,4 @@ +import { CosmereActor } from '../documents'; import { getSystemKeybinding, getSystemSetting, @@ -168,3 +169,49 @@ export function getApplyTargets() { return new Set([...selectTokens, ...targetTokens]); } + +export interface TargetDescriptor { + /** + * The UUID of the target. + */ + uuid: string; + + /** + * The target's name. + */ + name: string; + + /** + * The target's image. + */ + img: string; + + /** + * The target's defense values. + */ + def: { + phy: number; + cog: number; + spi: number; + }; +} + +/** + * Grab the targeted tokens and return relevant information on them. + * @returns {TargetDescriptor[]} + */ +export function getTargetDescriptors() { + const targets = new Map(); + for (const token of game.user!.targets) { + const { name, img, system, uuid } = (token.actor as CosmereActor) ?? {}; + const phy = system.defenses.phy.value.value ?? 10; + const cog = system.defenses.cog.value.value ?? 10; + const spi = system.defenses.spi.value.value ?? 10; + + if (uuid) { + targets.set(uuid, { name, img, uuid, def: { phy, cog, spi } }); + } + } + + return Array.from(targets.values()); +} diff --git a/src/system/utils/templates.ts b/src/system/utils/templates.ts index 9bc086a5..bcb3a6c7 100644 --- a/src/system/utils/templates.ts +++ b/src/system/utils/templates.ts @@ -52,6 +52,8 @@ export const TEMPLATES = { CHAT_CARD_INJURY: 'chat/card-injury.hbs', CHAT_CARD_DAMAGE_BUTTONS: 'chat/card-damage-buttons.hbs', + CHAT_CARD_TRAY_TARGETS: 'chat/card-tray-targets.hbs', + CHAT_ROLL_D20: 'chat/roll-d20.hbs', CHAT_ROLL_DAMAGE: 'chat/roll-damage.hbs', CHAT_ROLL_TOOLTIP: 'chat/roll-tooltip.hbs', diff --git a/src/templates/chat/card-tray-targets.hbs b/src/templates/chat/card-tray-targets.hbs new file mode 100644 index 00000000..1d178266 --- /dev/null +++ b/src/templates/chat/card-tray-targets.hbs @@ -0,0 +1,26 @@ +
+ +
+
+
+ {{ localize "COSMERE.AttributeGroup.Physical.short" }} + {{ localize "COSMERE.AttributeGroup.Cognitive.short" }} + {{ localize "COSMERE.AttributeGroup.Spiritual.short" }} +
+
    + {{#each targets}} +
  • + {{this.name}} + {{this.phyDef}} {{{this.phyIcon}}} + {{this.cogDef}} {{{this.cogIcon}}} + {{this.spiDef}} {{{this.spiIcon}}} +
  • + {{/each}} +
+
+
+
\ No newline at end of file From a2379182ce7b2ae16d9a2b3d35dbb2fcabc5fc47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mang=C3=B3?= Date: Sun, 17 Nov 2024 16:39:16 +0100 Subject: [PATCH 2/3] Make targets in tray clickable --- .../foundry/client/data/documents/actor.d.ts | 15 ++++++++++ src/system/documents/chat-message.ts | 30 +++++++++++++++++++ src/templates/chat/card-tray-targets.hbs | 2 +- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/declarations/foundry/client/data/documents/actor.d.ts b/src/declarations/foundry/client/data/documents/actor.d.ts index 119c9bb3..d46081dd 100644 --- a/src/declarations/foundry/client/data/documents/actor.d.ts +++ b/src/declarations/foundry/client/data/documents/actor.d.ts @@ -30,7 +30,9 @@ declare class Actor< get items(): Collection; get effects(): Collection; + get isToken(): boolean; get appliedEffects(): ActiveEffect[]; + get token(): TokenDocument; /** * Return a data object which defines the data schema against which dice rolls can be evaluated. @@ -55,6 +57,19 @@ declare class Actor< options?: Actor.ToggleStatusEffectOptions, ): Promise; + /** + * Retrieve an Array of active tokens which represent this Actor in the current canvas Scene. + * If the canvas is not currently active, or there are no linked actors, the returned Array will be empty. + * If the Actor is a synthetic token actor, only the exact Token which it represents will be returned. + * @param linked Limit results to Tokens which are linked to the Actor. Otherwise, return all Tokens even those which are not linked. + * @param document Return the Document instance rather than the PlaceableObject + * @returns An array of Token instances in the current Scene which reference this Actor. + */ + public getActiveTokens( + linked?: boolean, + document?: boolean + ): (TokenDocument | Token)[]; + /** * Get all ActiveEffects that may apply to this Actor. * If CONFIG.ActiveEffect.legacyTransferral is true, this is equivalent to actor.effects.contents. diff --git a/src/system/documents/chat-message.ts b/src/system/documents/chat-message.ts index d0369b14..23dd811a 100644 --- a/src/system/documents/chat-message.ts +++ b/src/system/documents/chat-message.ts @@ -203,6 +203,7 @@ export class CosmereChatMessage extends ChatMessage { for (const target of targets) { targetData.push({ name: target.name, + uuid: target.uuid, phyDef: target.def.phy, phyIcon: (d20Roll.total ?? 0) >= target.def.phy ? success : failure, @@ -224,6 +225,10 @@ export class CosmereChatMessage extends ChatMessage { const tray = $(trayHTML as unknown as HTMLElement); + tray.find('li.target').on('click', (event) => { + this.onClickTarget(event); + }); + html.find('.chat-card').append(tray); } @@ -644,6 +649,31 @@ export class CosmereChatMessage extends ChatMessage { target?.classList.toggle('expanded'); } + /** + * Handle target selection and panning. + * @param {Event} event The triggering event. + * @returns {Promise} A promise that resolves once the canvas pan has completed. + * @protected + */ + private async onClickTarget(event: JQuery.ClickEvent) { + event.stopPropagation(); + const uuid = (event.currentTarget as HTMLElement).dataset.uuid; + + if (!uuid) return; + + const actor = fromUuidSync(uuid) as CosmereActor; + const token = actor?.getActiveTokens()[0] as Token; + + if (!token) return; + + const releaseOthers = !event.shiftKey; + if (token.controlled) token.release(); + else { + token.control({ releaseOthers }); + return game.canvas!.animatePan(token.center); + } + } + /** * Handles hover begin events on the given html/jquery object. * @param {JQuery} html The object to handle hover begin events for. diff --git a/src/templates/chat/card-tray-targets.hbs b/src/templates/chat/card-tray-targets.hbs index 1d178266..3f2a0ef9 100644 --- a/src/templates/chat/card-tray-targets.hbs +++ b/src/templates/chat/card-tray-targets.hbs @@ -13,7 +13,7 @@
    {{#each targets}} -
  • +
  • {{this.name}} {{this.phyDef}} {{{this.phyIcon}}} {{this.cogDef}} {{{this.cogIcon}}} From 05393cc0bf529851b6898bad1dc374be608ec227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mang=C3=B3?= Date: Sun, 17 Nov 2024 17:22:43 +0100 Subject: [PATCH 3/3] Void async call --- src/system/documents/chat-message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/system/documents/chat-message.ts b/src/system/documents/chat-message.ts index 23dd811a..75e87447 100644 --- a/src/system/documents/chat-message.ts +++ b/src/system/documents/chat-message.ts @@ -226,7 +226,7 @@ export class CosmereChatMessage extends ChatMessage { const tray = $(trayHTML as unknown as HTMLElement); tray.find('li.target').on('click', (event) => { - this.onClickTarget(event); + void this.onClickTarget(event); }); html.find('.chat-card').append(tray);