diff --git a/.idea/runConfigurations/FoundryV14.xml b/.idea/runConfigurations/FoundryV14.xml deleted file mode 100644 index e17edefc..00000000 --- a/.idea/runConfigurations/FoundryV14.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/FoundryV14__Next_.xml b/.idea/runConfigurations/FoundryV14__Next_.xml new file mode 100644 index 00000000..2051c133 --- /dev/null +++ b/.idea/runConfigurations/FoundryV14__Next_.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/browser-tests/e2e/mighty-deeds.spec.js b/browser-tests/e2e/mighty-deeds.spec.js new file mode 100644 index 00000000..8b4616e3 --- /dev/null +++ b/browser-tests/e2e/mighty-deeds.spec.js @@ -0,0 +1,269 @@ +const { test, expect } = require('@playwright/test') + +/** + * E2E tests for Mighty Deed table prompts (issue #319) + * Create a world deed table, attack with a warrior until the deed die + * succeeds (3+), and verify the attack chat card offers the deed table + * prompt and that clicking Roll Deed posts the table result to chat. + * + * PREREQUISITES: + * 1. Start Foundry: npx @foundryvtt/foundryvtt-cli launch --world=v14 + * 2. Run tests: npm test + * + * The tests will automatically log in as Gamemaster (no password). + */ + +/* global game, ui, Actor, RollTable, CONFIG, CONST */ + +/** + * Create the test fixtures in the live world: a Mighty Deed roll table + * and a warrior with a deed die attack bonus and an equipped weapon. + * @param {import('@playwright/test').Page} page + * @returns {Promise<{actorId: string, weaponId: string, registryEntry: object}>} + */ +async function createDeedFixtures (page) { + return page.evaluate(async () => { + await RollTable.create({ + name: 'E2E Deed Table', + formula: '1d7', + results: [ + { type: CONST.TABLE_RESULT_TYPES.TEXT, description: 'Off-balance: enemy gets a Ref save or is knocked prone.', range: [3, 3] }, + { type: CONST.TABLE_RESULT_TYPES.TEXT, description: 'Knockdown: a human-sized opponent is knocked prone.', range: [4, 4] }, + { type: CONST.TABLE_RESULT_TYPES.TEXT, description: 'Throw: the opponent is knocked down and thrown 10 feet.', range: [5, 99] } + ] + }) + + const actor = await Actor.create({ + name: 'E2E Warrior', + type: 'Player', + system: { + details: { sheetClass: 'Warrior', attackBonus: '+d4' }, + class: { className: 'Warrior' }, + config: { attackBonusMode: 'autoPerAttack' } + } + }) + const [weapon] = await actor.createEmbeddedDocuments('Item', [{ + name: 'E2E Longsword', + type: 'weapon', + system: { actionDie: '1d20', toHit: '@ab', damage: '1d8+@ab', melee: true, equipped: true } + }]) + + return { + actorId: actor.id, + weaponId: weapon.id, + registryEntry: CONFIG.DCC.mightyDeedsTables['E2E Deed Table'] + } + }) +} + +/** + * Attack with the warrior's weapon until the deed die result matches the + * wanted success state, and return that attack's chat message data. + * @param {import('@playwright/test').Page} page + * @param {{actorId: string, weaponId: string}} ids + * @param {boolean} wantSuccess - true to stop on a deed of 3+, false to stop on a failed deed + */ +async function attackUntilDeed (page, ids, wantSuccess) { + return page.evaluate(async ({ actorId, weaponId, wantSuccess }) => { + const actor = game.actors.get(actorId) + for (let attempt = 0; attempt < 30; attempt++) { + const before = game.messages.size + await actor.rollWeaponAttack(weaponId) + // rollWeaponAttack does not await its ChatMessage.create, so wait for the card to land + for (let w = 0; w < 50 && game.messages.size === before; w++) { + await new Promise(resolve => setTimeout(resolve, 100)) + } + const msg = game.messages.contents.at(-1) + if (game.messages.size > before && Boolean(msg.system?.deedRollSuccess) === wantSuccess) { + return { + messageId: msg.id, + deedDieRollResult: msg.system.deedDieRollResult, + deedRollSuccess: msg.system.deedRollSuccess, + deedTables: msg.system.deedTables, + contentHasPrompt: msg.content.includes('deed-table-prompt') + } + } + } + return null + }, { ...ids, wantSuccess }) +} + +/** + * Toggle the off-by-default `mightyDeedsEnabled` world setting (issue #319). + * @param {import('@playwright/test').Page} page + * @param {boolean} enabled + */ +async function setMightyDeedsEnabled (page, enabled) { + await page.evaluate((value) => game.settings.set('dcc', 'mightyDeedsEnabled', value), enabled) +} + +test.describe('Mighty Deeds E2E Tests', () => { + let consoleErrors = [] + + test.beforeAll(async () => { + let serverUp + try { + const response = await fetch('http://localhost:30000/', { signal: AbortSignal.timeout(5000) }) + serverUp = response.ok + } catch { + serverUp = false + } + if (!serverUp) { + throw new Error( + 'Could not connect to Foundry VTT at http://localhost:30000.\n\n' + + 'Please start Foundry before running tests:\n' + + '1. Run: npx @foundryvtt/foundryvtt-cli launch --world=v14\n' + + '2. Run tests again: npm test' + ) + } + }) + + test.beforeEach(async ({ page }) => { + consoleErrors = [] + page.on('console', msg => { + if (msg.type() === 'error') { + consoleErrors.push(msg.text()) + } + }) + + await page.setViewportSize({ width: 1280, height: 800 }) + + await page.goto('http://localhost:30000/join') + await page.waitForTimeout(1000) + + const isInGame = await page.locator('.game.system-dcc').isVisible({ timeout: 1000 }).catch(() => false) + + if (!isInGame) { + const userSelect = page.locator('select[name="userid"]') + await userSelect.waitFor({ state: 'visible', timeout: 10000 }) + await page.selectOption('select[name="userid"]', { label: 'Gamemaster' }) + await page.click('button[name="join"]') + await page.waitForSelector('.game.system-dcc', { timeout: 30000 }) + } + + await page.waitForSelector('#actors', { timeout: 10000, state: 'attached' }) + + // System settings register in the async ready hook on every client + // load - sheet renders read them, so wait until registration is done + await page.waitForFunction(() => game.settings?.settings?.has('dcc.coinWeight'), { timeout: 15000 }) + + // Remove any Foundry notification banners + await page.evaluate(() => document.querySelectorAll('#notifications .notification').forEach(n => n.remove())) + + // Clean up leftover test entities from previous runs + await page.evaluate(async () => { + for (const actor of game.actors.filter(a => a.name.startsWith('E2E '))) { + await actor.delete() + } + for (const table of game.tables.filter(t => t.name.startsWith('E2E '))) { + await table.delete() + } + }) + + // Reset the deed prompt to its off-by-default state for test isolation (issue #319) + await setMightyDeedsEnabled(page, false) + + // Close any welcome dialogs + for (const selector of ['#dcc-welcome-dialog', '#dcc-core-book-welcome-dialog']) { + const dialog = page.locator(selector) + if (await dialog.isVisible({ timeout: 500 }).catch(() => false)) { + await page.keyboard.press('Escape') + await page.waitForTimeout(300) + } + } + }) + + test.afterEach(async () => { + const significantErrors = consoleErrors.filter(err => !err.includes('favicon.ico')) + expect(significantErrors, `Console errors detected: ${significantErrors.join('\n')}`).toHaveLength(0) + }) + + test('world tables with Deed in the name register and unregister', async ({ page }) => { + const fixtures = await createDeedFixtures(page) + + // The createRollTable hook picked the world table up immediately + expect(fixtures.registryEntry).toEqual({ name: 'E2E Deed Table', path: 'E2E Deed Table' }) + + // Deleting the table removes it from the registry + const afterDelete = await page.evaluate(async () => { + await game.tables.getName('E2E Deed Table').delete() + return CONFIG.DCC.mightyDeedsTables['E2E Deed Table'] || null + }) + expect(afterDelete).toBeNull() + }) + + test('a successful deed offers the table prompt and Roll Deed posts the result', async ({ page }) => { + const fixtures = await createDeedFixtures(page) + await setMightyDeedsEnabled(page, true) + + const success = await attackUntilDeed(page, fixtures, true) + expect(success, 'no successful deed in 30 attacks').not.toBeNull() + expect(success.deedDieRollResult).toBeGreaterThanOrEqual(3) + // Other deed tables may be registered too (e.g. the dcc-core-book pack) + expect(success.deedTables).toContainEqual({ name: 'E2E Deed Table', path: 'E2E Deed Table' }) + expect(success.contentHasPrompt).toBe(true) + + // Select the test table and click Roll Deed on the rendered chat card + await page.evaluate(() => ui.sidebar.expand()) + await page.click('button[data-tab="chat"]') + await page.waitForTimeout(500) + await page.evaluate(() => ui.chat.scrollBottom({ immediate: true })) + const card = page.locator(`#chat .chat-message[data-message-id="${success.messageId}"]`) + await card.locator('.deed-table-select').selectOption('E2E Deed Table') + const button = card.locator('.roll-deed-table') + await button.scrollIntoViewIfNeeded() + await expect(button).toBeVisible() + + const messagesBefore = await page.evaluate(() => game.messages.size) + await button.click() + await expect.poll(async () => { + return page.evaluate(() => game.messages.size) + }, { timeout: 10000 }).toBeGreaterThan(messagesBefore) + + const result = await page.evaluate(() => { + const msg = game.messages.contents.at(-1) + return { flavor: msg.flavor, content: msg.content, isMightyDeed: msg.getFlag('dcc', 'isMightyDeed') } + }) + expect(result.isMightyDeed).toBe(true) + expect(result.flavor).toContain('E2E Deed Table') + expect(result.flavor).toContain(`(${success.deedDieRollResult})`) + // The posted result is the table entry matching the deed die value + const expected = { + 3: 'Off-balance', + 4: 'Knockdown' + }[success.deedDieRollResult] || 'Throw' + expect(result.content).toContain(expected) + + // One-shot: the button disables after posting and a second click adds no further result + await expect(button).toBeDisabled() + const countAfterFirst = await page.evaluate(() => game.messages.size) + await button.click({ force: true }).catch(() => {}) + await page.waitForTimeout(500) + expect(await page.evaluate(() => game.messages.size)).toBe(countAfterFirst) + }) + + test('a failed deed shows no table prompt', async ({ page }) => { + const fixtures = await createDeedFixtures(page) + await setMightyDeedsEnabled(page, true) + + const failure = await attackUntilDeed(page, fixtures, false) + expect(failure, 'no failed deed in 30 attacks').not.toBeNull() + expect(failure.deedDieRollResult).toBeLessThan(3) + expect(failure.deedTables).toEqual([]) + expect(failure.contentHasPrompt).toBe(false) + }) + + test('with the setting disabled (default), a successful deed shows no prompt', async ({ page }) => { + const fixtures = await createDeedFixtures(page) + // mightyDeedsEnabled is left at its default (false) by beforeEach. + // The table is still registered, but the attack card must not offer it. + expect(fixtures.registryEntry).toEqual({ name: 'E2E Deed Table', path: 'E2E Deed Table' }) + + const success = await attackUntilDeed(page, fixtures, true) + expect(success, 'no successful deed in 30 attacks').not.toBeNull() + expect(success.deedDieRollResult).toBeGreaterThanOrEqual(3) + // Feature off: no tables attached and no prompt rendered even on a deed success + expect(success.deedTables).toEqual([]) + expect(success.contentHasPrompt).toBe(false) + }) +}) diff --git a/browser-tests/e2e/playwright.config.js b/browser-tests/e2e/playwright.config.js index d939f8d2..94cd3f72 100644 --- a/browser-tests/e2e/playwright.config.js +++ b/browser-tests/e2e/playwright.config.js @@ -15,7 +15,13 @@ module.exports = defineConfig({ timeout: 60000, // 60 seconds per test use: { baseURL: 'http://localhost:30000', - trace: 'on-first-retry' + trace: 'on-first-retry', + launchOptions: { + // Hardware-accelerated WebGL in headless Chromium (Metal on macOS). + // Without it Foundry detects SwiftShader and shows a permanent + // "hardware acceleration" banner that intercepts clicks in tests. + args: process.platform === 'darwin' ? ['--use-angle=metal'] : [] + } }, projects: [ { diff --git a/docs/dev/EXTENSION_API.md b/docs/dev/EXTENSION_API.md index 039c08e1..8f8502a0 100644 --- a/docs/dev/EXTENSION_API.md +++ b/docs/dev/EXTENSION_API.md @@ -89,6 +89,7 @@ audited sibling modules for the path before slimming. | `dcc.postActorImport` | `module/parser.js:273` | `xcc` (self-emission) | §2.5 | Fired after Purple Sorcerer / stat-block import. | | `dcc.registerCriticalHitsPack` | `module/settings.js:76` | `dcc-core-book`, `xcc-core-book` (emitters) | §2.10, §2.11 | Emitted by settings change AND re-emitted by content packs during `dcc.ready`. | | `dcc.registerDisapprovalPack` | `module/settings.js:124` | `dcc-core-book`, `xcc-core-book`, `dcc-annual-1` (emitters) | §2.10, §2.11 | Same pattern. | +| `dcc.registerMightyDeedsPack` | `module/settings.js` (onChange emitter); handler in `module/settings-table-hooks.mjs` | `dcc-core-book` (companion pack, emitter) | §2.10, §2.11 | Same pattern as `registerDisapprovalPack`. Registers a compendium of Mighty Deed result tables surfaced on the attack card's deed prompt (issue #319). Emitted by the `mightyDeedsCompendium` settings change AND re-emitted by content packs during `dcc.ready`. The prompt itself is gated behind the off-by-default `mightyDeedsEnabled` world setting. | | `dcc.registerLevelDataPack` | *(system listens; emitted by packs)* | `dcc-core-book`, `xcc-core-book`, `dcc-crawl-classes` (emitters); system listens at `dcc.js:923` | §2.10, §2.11 | System is a *listener* here, not an emitter. Class progressions come in through this. | | `dcc.setFumbleTable` | `module/settings.js:108` | `dcc-core-book`, `xcc-core-book` (emitters) | §2.10 | | | `dcc.setDivineAidTable` | `module/settings.js:172` | `dcc-core-book` (emitter) | §2.10 | | diff --git a/docs/user-guide/Mighty-Deeds.md b/docs/user-guide/Mighty-Deeds.md index 13245639..ad765907 100644 --- a/docs/user-guide/Mighty-Deeds.md +++ b/docs/user-guide/Mighty-Deeds.md @@ -30,3 +30,19 @@ You can use `+@ab` in your weapon's to hit and damage fields to include the deed See [Advanced Character Settings](Advanced-Character-Settings.md) for more details on the Attack Bonus Mode setting. +## Mighty Deed Table Prompt (optional) + +When enabled, a successful deed (a deed die of **3 or higher**) adds a prompt to the attack chat card: a dropdown of available Mighty Deed tables plus a **Roll Deed** button. Pick the table for the deed you declared and click **Roll Deed** to look the deed die result up on that table and post the outcome to chat. + +This feature is **off by default**. To turn it on: + +1. Open **Game Settings → Configure Settings → Dungeon Crawl Classics** +2. Enable **Enable Mighty Deed Tables** + +Tables are gathered from two places: + +- **World roll tables** whose name contains **"Deed"** are picked up automatically (created, renamed, and deleted tables update live). +- A **Mighty Deeds Tables Compendium** can be selected under the manual compendium settings; modules (such as the core rulebook content) can also register deed-table packs via the `dcc.registerMightyDeedsPack` hook. + +If no deed tables exist, or the deed fails, the attack card is unchanged and no prompt appears. + diff --git a/lang/cn.json b/lang/cn.json index 0eb47c12..7722253f 100644 --- a/lang/cn.json +++ b/lang/cn.json @@ -239,6 +239,7 @@ "DCC.DamageDie": "伤害骰", "DCC.DamageModifier": "伤害调整值", "DCC.DamageRollInvalidFormulaInline": "无效伤害公式`{formula}`<\/b>", + "DCC.Deed": "壮举", "DCC.DeedDie": "壮举骰", "DCC.DeedRoll": "投壮举骰", "DCC.Deity": "神祇", @@ -467,6 +468,9 @@ "DCC.MercurialMagicRerollPrompt": "重投或查阅无常魔法投骰?", "DCC.MercurialMagicRoll": "无常魔法投骰", "DCC.MercurialTabHint": "为此法术投骰并设置无常魔法", + "DCC.MightyDeedRollFlavor": "在{table}上的武勇壮举({roll})", + "DCC.MightyDeedTableNotFound": "找不到壮举表{table}", + "DCC.MightyDeedTableSelectHint": "选择用于查询壮举骰结果的壮举表", "DCC.MightyDeedsHowToLink": "武勇壮举使用方法", "DCC.MightyDeedsLink": "@UUID[Compendium.dcc-core-book.dcc-core-text.JournalEntry.n5gqDCyFO09A3GAJ.JournalEntryPage.UrVBDNG5csbS5bBa#mighty-deeds-of-arms]{武勇壮举}", "DCC.MightyDeedsOfArms": "武勇壮举", @@ -540,6 +544,7 @@ "DCC.ResolveValueEmote": "已投骰{itemName}价值:{pp}pp{ep}ep{gp}gp{sp}sp{cp}cp", "DCC.Roll": "投骰", "DCC.RollCritical": "投骰大成功", + "DCC.RollDeed": "投壮举", "DCC.RolledAbilityEmote": "{actorName} 投骰 {abilityName} 出 {abilityInlineRollHTML}。", "DCC.AbilityCheckPenaltyNote": "

若应用检定惩罚,总计为 {total}。<\/p>", "DCC.RolledCritEmote": "{actorName} 投骰大成功表{critTableName}{critResult} 出 {critInlineRollHTML}", @@ -639,6 +644,10 @@ "DCC.SettingManualCompendiumConfigurationHint": "不使用核心书模组,而是自行建立数据库合集——不推荐", "DCC.SettingMercurialMagicTable": "无常魔法表", "DCC.SettingMercurialMagicTableHint": "为无常魔法投骰表格——必须在合集中存在", + "DCC.SettingMightyDeedsEnabled": "启用壮举表", + "DCC.SettingMightyDeedsEnabledHint": "当战士的壮举骰成功(3 或更高)时,在攻击卡上提供壮举表提示。默认关闭。", + "DCC.SettingMightyDeedsTablesCompendium": "壮举表合集", + "DCC.SettingMightyDeedsTablesCompendiumHint": "用于查找壮举表的合集。名称中包含'Deed'的世界表也会出现在攻击骰聊天卡上", "DCC.SettingShowRollModifierByDefault": "默认显示调整投骰对话框", "DCC.SettingShowRollModifierByDefaultHint": "无需按住 ⌘\/CTRL,点击角色卡物品就显示调整投骰对话框", "DCC.SettingTurnUnholyTable": "驱散亵渎表", diff --git a/lang/de.json b/lang/de.json index 15ddbe92..cf18a4dd 100644 --- a/lang/de.json +++ b/lang/de.json @@ -239,6 +239,7 @@ "DCC.DamageDie": "Schadenswürfel", "DCC.DamageModifier": "Schadensmodifikator", "DCC.DamageRollInvalidFormulaInline": "Ungültige Schadensformel '{formula}'", + "DCC.Deed": "Großtat", "DCC.DeedDie": "Kriegerwürfel", "DCC.DeedRoll": "Großtatwurf", "DCC.Deity": "Gottheit", @@ -467,6 +468,9 @@ "DCC.MercurialMagicRerollPrompt": "Launenhaften Effekt nachschlagen oder erneut auswürfeln?", "DCC.MercurialMagicRoll": "Launenhafter Effekt", "DCC.MercurialTabHint": "Würfle und konfiguriere launenhafte Magie für diesen Zauber", + "DCC.MightyDeedRollFlavor": "Großtat ({roll}) auf {table}", + "DCC.MightyDeedTableNotFound": "Großtat-Tabelle {table} nicht gefunden", + "DCC.MightyDeedTableSelectHint": "Wähle die Großtat-Tabelle, auf der das Kriegerwürfel-Ergebnis nachgeschlagen wird", "DCC.MightyDeedsHowToLink": "Großtaten How-To", "DCC.MightyDeedsLink": "@UUID[Compendium.dcc-core-book.dcc-core-text.JournalEntry.n5gqDCyFO09A3GAJ.JournalEntryPage.UrVBDNG5csbS5bBa#mighty-deeds-of-arms]{Mighty Deeds of Arms}", "DCC.MightyDeedsOfArms": "Großtaten des Kriegers", @@ -540,6 +544,7 @@ "DCC.ResolveValueEmote": "Erwürfelter Wert von {itemName}:
{pp} PM {ep} EM {gp} GM {sp} SM {cp} KM", "DCC.Roll": "Wurf", "DCC.RollCritical": "Kritischen Treffer würfeln", + "DCC.RollDeed": "Großtat würfeln", "DCC.RolledAbilityEmote": "{actorName} würfelte {abilityInlineRollHTML} für {abilityName}.", "DCC.AbilityCheckPenaltyNote": "

Wenn Kontrollabzug angewendet wird, beträgt die Summe {total}.

", "DCC.RolledCritEmote": "{actorName} würfelte {critInlineRollHTML} auf Krit-Tabelle {critTableName}{critResult}", @@ -639,6 +644,10 @@ "DCC.SettingManualCompendiumConfigurationHint": "Falls du deine eigenen Nachschlage-Kompendien erstellen möchtest, anstatt das Grundregelbuch-Modul zu verwenden - nicht empfohlen", "DCC.SettingMercurialMagicTable": "Tabelle Launenhafte Magie", "DCC.SettingMercurialMagicTableHint": "Würfeltabelle für Launenhafte Magie - muss in einem Kompendium liegen", + "DCC.SettingMightyDeedsEnabled": "Großtat-Tabellen aktivieren", + "DCC.SettingMightyDeedsEnabledHint": "Bietet auf Angriffskarten eine Großtat-Tabellenauswahl an, wenn der Großtat-Würfel eines Kriegers erfolgreich ist (3 oder höher). Standardmäßig deaktiviert.", + "DCC.SettingMightyDeedsTablesCompendium": "Kompendium für Großtat-Tabellen", + "DCC.SettingMightyDeedsTablesCompendiumHint": "Kompendium, in dem nach Großtat-Tabellen gesucht wird. Welttabellen mit 'Deed' im Namen werden ebenfalls auf der Angriffswurf-Chatkarte angeboten", "DCC.SettingShowRollModifierByDefault": "Zeige standardmäßig Würfeldialog", "DCC.SettingShowRollModifierByDefaultHint": "Vertausche das Standardverhalten des Würfeldialogs, so dass er immer erscheint, außer bei Strg-Klick auf das Würfelsymbol.", "DCC.SettingTurnUnholyTable": "Tabelle Unheiliges vertreiben", diff --git a/lang/en.json b/lang/en.json index 6a48ec22..645b3ca1 100644 --- a/lang/en.json +++ b/lang/en.json @@ -239,6 +239,7 @@ "DCC.DamageDie": "Damage Die", "DCC.DamageModifier": "Damage Modifier", "DCC.DamageRollInvalidFormulaInline": "Invalid Damage formula '{formula}'", + "DCC.Deed": "Deed", "DCC.DeedDie": "Deed Die", "DCC.DeedRoll": "Roll Deed Die", "DCC.Deity": "Deity", @@ -467,6 +468,9 @@ "DCC.MercurialMagicRerollPrompt": "Re-roll or Lookup Mercurial Magic Roll?", "DCC.MercurialMagicRoll": "Mercurial Magic Roll", "DCC.MercurialTabHint": "Roll and configure mercurial magic for this spell", + "DCC.MightyDeedRollFlavor": "Mighty Deed ({roll}) on {table}", + "DCC.MightyDeedTableNotFound": "Unable to find Mighty Deed table {table}", + "DCC.MightyDeedTableSelectHint": "Choose the Mighty Deed table to look the deed die result up on", "DCC.MightyDeedsHowToLink": "Mighty Deeds How-To", "DCC.MightyDeedsLink": "@UUID[Compendium.dcc-core-book.dcc-core-text.JournalEntry.n5gqDCyFO09A3GAJ.JournalEntryPage.UrVBDNG5csbS5bBa#mighty-deeds-of-arms]{Mighty Deeds of Arms}", "DCC.MightyDeedsOfArms": "Mighty Deeds of Arms", @@ -540,6 +544,7 @@ "DCC.ResolveValueEmote": "Rolled value of {itemName}:
{pp} Pp. {ep} Ep. {gp} Gp. {sp} Sp. {cp} Cp.", "DCC.Roll": "Roll", "DCC.RollCritical": "Roll Critical", + "DCC.RollDeed": "Roll Deed", "DCC.RolledAbilityEmote": "{actorName} rolled {abilityInlineRollHTML} for their {abilityName}.", "DCC.AbilityCheckPenaltyNote": "

If check penalty applies, total is {total}.

", "DCC.RolledCritEmote": "{actorName} rolled {critInlineRollHTML} on Crit Table {critTableName}{critResult}", @@ -639,6 +644,10 @@ "DCC.SettingManualCompendiumConfigurationHint": "If you want to build your own lookup compendia instead of using the Core Book Module - not recommended", "DCC.SettingMercurialMagicTable": "Mercurial Magic Table", "DCC.SettingMercurialMagicTableHint": "Roll Table to use for Mercurial Magic effects - must be in a compendium pack", + "DCC.SettingMightyDeedsEnabled": "Enable Mighty Deed Tables", + "DCC.SettingMightyDeedsEnabledHint": "Offer a Mighty Deed table prompt on attack cards when a warrior's deed die succeeds (3 or higher). Off by default.", + "DCC.SettingMightyDeedsTablesCompendium": "Mighty Deeds Tables Compendium", + "DCC.SettingMightyDeedsTablesCompendiumHint": "Compendium to look in for Mighty Deed tables. World tables with 'Deed' in the name are also offered on the attack roll chat card", "DCC.SettingShowRollModifierByDefault": "Show the Modify Roll dialog by default", "DCC.SettingShowRollModifierByDefaultHint": "Show Roll Modifier dialog without having to CMD/CTRL click on character sheet items", "DCC.SettingTurnUnholyTable": "Turn Unholy Table", diff --git a/lang/es.json b/lang/es.json index 03316764..961216ac 100644 --- a/lang/es.json +++ b/lang/es.json @@ -239,6 +239,7 @@ "DCC.DamageDie": "Dado de daño", "DCC.DamageModifier": "Modificador de daño", "DCC.DamageRollInvalidFormulaInline": "Fórmula inválida de daño '{formula}'", + "DCC.Deed": "Hazaña", "DCC.DeedDie": "Dado de acción", "DCC.DeedRoll": "Tirar dado de acción", "DCC.Deity": "Deidad", @@ -467,6 +468,9 @@ "DCC.MercurialMagicRerollPrompt": "¿Volver a tirar o buscar la tirada de Magia Mercurial?", "DCC.MercurialMagicRoll": "Tirada de Magia Mercurial", "DCC.MercurialTabHint": "Tirar y configurar magia mercurial para este hechizo", + "DCC.MightyDeedRollFlavor": "Hazaña poderosa ({roll}) en {table}", + "DCC.MightyDeedTableNotFound": "No se encuentra la tabla de hazañas {table}", + "DCC.MightyDeedTableSelectHint": "Elige la tabla de hazañas en la que consultar el resultado del dado de acción", "DCC.MightyDeedsHowToLink": "Cómo hacer Hazañas Asombrosas", "DCC.MightyDeedsLink": "@UUID[Compendium.dcc-core-book.dcc-core-text.JournalEntry.n5gqDCyFO09A3GAJ.JournalEntryPage.UrVBDNG5csbS5bBa#mighty-deeds-of-arms]{Hazañas poderosas de armas}", "DCC.MightyDeedsOfArms": "Hazañas poderosas de armas", @@ -540,6 +544,7 @@ "DCC.ResolveValueEmote": "Valor tirado de {itemName}:
{pp} pp. {ep} ep. {gp} gp. {sp} sp. {cp} cp.", "DCC.Roll": "Tirar", "DCC.RollCritical": "Tirada crítica", + "DCC.RollDeed": "Tirar hazaña", "DCC.RolledAbilityEmote": "{actorName} tiró {abilityInlineRollHTML} para su {abilityName}.", "DCC.AbilityCheckPenaltyNote": "

Si se aplica la penalización de control, el total es {total}.

", "DCC.RolledCritEmote": "{actorName} tiró {critInlineRollHTML} en la tabla de críticos {critTableName}{critResult}", @@ -639,6 +644,10 @@ "DCC.SettingManualCompendiumConfigurationHint": "Si quieres crear tus propios compendios de consulta en lugar de usar el módulo Core Book - no recomendado", "DCC.SettingMercurialMagicTable": "Tabla de Magia Mercurial", "DCC.SettingMercurialMagicTableHint": "Tabla para usar en efectos de magia mercurial - debe estar en un paquete de compendio", + "DCC.SettingMightyDeedsEnabled": "Habilitar Tablas de Hazañas", + "DCC.SettingMightyDeedsEnabledHint": "Ofrece una selección de tabla de Hazañas en las cartas de ataque cuando el dado de hazaña de un guerrero tiene éxito (3 o más). Desactivado por defecto.", + "DCC.SettingMightyDeedsTablesCompendium": "Compendio de Tablas de Hazañas", + "DCC.SettingMightyDeedsTablesCompendiumHint": "Compendio donde buscar tablas de hazañas poderosas. Las tablas del mundo con 'Deed' en el nombre también se ofrecen en la tarjeta de chat de la tirada de ataque", "DCC.SettingShowRollModifierByDefault": "Mostrar diálogo de modificar tirada por defecto", "DCC.SettingShowRollModifierByDefaultHint": "Mostrar diálogo de modificador sin tener que CMD/CTRL clic en elementos de la hoja", "DCC.SettingTurnUnholyTable": "Tabla de Rechazo a lo Impío", diff --git a/lang/fr.json b/lang/fr.json index 6f265be4..d45b282f 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -225,6 +225,7 @@ "DCC.DamageDie": "Dé de dégat", "DCC.DamageModifier": "Mod. dégats", "DCC.DamageRollInvalidFormulaInline": " Formule de dégât invalide '{formula}'", + "DCC.Deed": "Haut fait", "DCC.DeedDie": "Dé de haut fait", "DCC.DeedRoll": "Jet de haut fait", "DCC.Deity": "Divinité", @@ -454,6 +455,9 @@ "DCC.MercurialMagicRerollPrompt": "Relancer ou choisir le résultat du jet de magie mercurielle?", "DCC.MercurialMagicRoll": "Jet de magie mercurielle", "DCC.MercurialTabHint": "Lancer et configurer la magie mercurielle pour ce sort", + "DCC.MightyDeedRollFlavor": "Haut fait ({roll}) sur {table}", + "DCC.MightyDeedTableNotFound": "Table de hauts faits {table} introuvable", + "DCC.MightyDeedTableSelectHint": "Choisissez la table de hauts faits sur laquelle consulter le résultat du dé de haut fait", "DCC.MightyDeedsHowToLink": "Guide des hauts faits", "DCC.MightyDeedsLink": "@Compendium[dcc-core-book.dcc-core-journals.1gcH2FKbeGbxWtIQ]{Haut fait d'armes}", "DCC.MightyDeedsOfArms": "Hauts faits d'arme", @@ -567,6 +571,7 @@ "DCC.RollTreasureValue": "Déterminer la valeur du trésor", "DCC.RollUnder": "{name} (Lancer en tant que)", "DCC.RollWisdomCheckHint": "Cliquez pour lancer un test de Sagesse, CMD/CTRL clic pour plus d'options", + "DCC.RollDeed": "Lancer le haut fait", "DCC.RolledAbilityEmote": "{actorName} a lancé {abilityInlineRollHTML} pour sa {abilityName}.", "DCC.AbilityCheckPenaltyNote": "

Si la pénalité de contrôle s'applique, le total est {total}.

", "DCC.RolledCritEmote": "{actorName} a lancé {critInlineRollHTML} sur Table Crit {critTableName}{critResult}", @@ -626,6 +631,10 @@ "DCC.SettingManualCompendiumConfigurationHint": "Si vous voulez construire vos propres compendia de recherche au lieu d'utiliser le Module Core Book - non recommandé", "DCC.SettingMercurialMagicTable": "Table de la magie mercurielle", "DCC.SettingMercurialMagicTableHint": "Table à utiliser pour les effets de la magie mercurielle - doit être dans un compendium pack", + "DCC.SettingMightyDeedsEnabled": "Activer les tables de hauts faits", + "DCC.SettingMightyDeedsEnabledHint": "Propose une sélection de table de hauts faits sur les cartes d'attaque lorsque le dé de haut fait d'un guerrier réussit (3 ou plus). Désactivé par défaut.", + "DCC.SettingMightyDeedsTablesCompendium": "Compendium des tables de hauts faits", + "DCC.SettingMightyDeedsTablesCompendiumHint": "Compendium où chercher les tables de hauts faits. Les tables du monde contenant 'Deed' dans leur nom sont aussi proposées sur la carte de jet d'attaque", "DCC.SettingShowRollModifierByDefault": "Montrer les modificateurs du jets", "DCC.SettingShowRollModifierByDefaultHint": "Inverse le comportement des modificateur de jets pour les appliquer par défaut sauf si le jet est CTRL-CLICKé.", "DCC.SettingSpellSideEffectsCompendium": "Compendium des Effets Secondaires de Sorts", diff --git a/lang/it.json b/lang/it.json index 64bd7389..5df883ec 100644 --- a/lang/it.json +++ b/lang/it.json @@ -239,6 +239,7 @@ "DCC.DamageDie": "Dado Danno", "DCC.DamageModifier": "Mod. Danno", "DCC.DamageRollInvalidFormulaInline": "Formula Danno non valida '{formula}'", + "DCC.Deed": "Cimento", "DCC.DeedDie": "Dado Cimento", "DCC.DeedRoll": "Tiro Dado Cimento", "DCC.Deity": "Divinità", @@ -467,6 +468,9 @@ "DCC.MercurialMagicRerollPrompt": "Tira ancora o Guarda?", "DCC.MercurialMagicRoll": "Tiri Magia Mercuriale", "DCC.MercurialTabHint": "Tira e configura l'effetto mercuriale per questo Incantesimo", + "DCC.MightyDeedRollFlavor": "Prode Cimento ({roll}) su {table}", + "DCC.MightyDeedTableNotFound": "Tabella dei Cimenti {table} non trovata", + "DCC.MightyDeedTableSelectHint": "Scegli la tabella dei Cimenti su cui consultare il risultato del Dado Cimento", "DCC.MightyDeedsHowToLink": "Guida ai Prodi Cimenti d'Arme", "DCC.MightyDeedsLink": "@UUID[Compendium.dcc-core-book.dcc-core-text.JournalEntry.n5gqDCyFO09A3GAJ.JournalEntryPage.UrVBDNG5csbS5bBa#mighty-deeds-of-arms]{Mighty Deeds of Arms}", "DCC.MightyDeedsOfArms": "Prode Cimento d'Arme", @@ -540,6 +544,7 @@ "DCC.ResolveValueEmote": "{itemName} vale:
{pp} mp. {ep} me. {gp} mo. {sp} ma. {cp} mr.", "DCC.Roll": "Tiro", "DCC.RollCritical": "Tira Critico", + "DCC.RollDeed": "Tira Cimento", "DCC.RolledAbilityEmote": "{actorName} tira {abilityInlineRollHTML} per {abilityName}", "DCC.AbilityCheckPenaltyNote": "

Se si applica la penalità di controllo, il totale è {total}.

", "DCC.RolledCritEmote": "{actorName} tira {critInlineRollHTML} sulla Tabella dei Critici {critTableName}{critResult}", @@ -639,6 +644,10 @@ "DCC.SettingManualCompendiumConfigurationHint": "Per quando si vuole costruire un proprio Compendio invece di usare il modulo Core Book, anche se non è consigliabile.", "DCC.SettingMercurialMagicTable": "Tabella Magia Mercuriale", "DCC.SettingMercurialMagicTableHint": "Tabella da usare per Magia Mercuriale - deve trovarsi in un Compendio", + "DCC.SettingMightyDeedsEnabled": "Abilita le Tabelle dei Cimenti", + "DCC.SettingMightyDeedsEnabledHint": "Offre una selezione della tabella dei Cimenti sulle carte di attacco quando il dado del cimento di un guerriero ha successo (3 o più). Disattivato per impostazione predefinita.", + "DCC.SettingMightyDeedsTablesCompendium": "Compendio delle Tabelle dei Cimenti", + "DCC.SettingMightyDeedsTablesCompendiumHint": "Compendio in cui cercare le tabelle dei Prodi Cimenti. Anche le tabelle del mondo con 'Deed' nel nome vengono offerte nella carta chat del tiro d'attacco", "DCC.SettingShowRollModifierByDefault": "Mostra il dialogo Modifica Tiro per impostazione predefinita", "DCC.SettingShowRollModifierByDefaultHint": "Mostra il dialogo Modifica Tiro senza dover cliccare CMD/CTRL sugli oggetti della scheda del personaggio", "DCC.SettingTurnUnholyTable": "Tabella Scacciare l'Empio", diff --git a/lang/pl.json b/lang/pl.json index c9488328..eaeae6d6 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -239,6 +239,7 @@ "DCC.DamageDie": "Kostka Obrażeń", "DCC.DamageModifier": "Modyfikator Obrażeń", "DCC.DamageRollInvalidFormulaInline": "Nieprawidłowa formuła Obrażeń '{formula}'", + "DCC.Deed": "Czyn", "DCC.DeedDie": "Kostka Czynu", "DCC.DeedRoll": "Rzuć Kostką Czynu", "DCC.Deity": "Bóstwo", @@ -467,6 +468,9 @@ "DCC.MercurialMagicRerollPrompt": "Przerzucić czy Sprawdzić Rzut Merkurialnej Magii?", "DCC.MercurialMagicRoll": "Rzut Merkurialnej Magii", "DCC.MercurialTabHint": "Rzuć i skonfiguruj merkurialną magię dla tego czaru", + "DCC.MightyDeedRollFlavor": "Potężny Czyn ({roll}) na {table}", + "DCC.MightyDeedTableNotFound": "Nie znaleziono tabeli Czynów {table}", + "DCC.MightyDeedTableSelectHint": "Wybierz tabelę Czynów, w której zostanie sprawdzony wynik Kostki Czynu", "DCC.MightyDeedsHowToLink": "Instrukcja Potężnych Czynów", "DCC.MightyDeedsLink": "@UUID[Compendium.dcc-core-book.dcc-core-text.JournalEntry.n5gqDCyFO09A3GAJ.JournalEntryPage.UrVBDNG5csbS5bBa#mighty-deeds-of-arms]{Potężne Czyny Broni}", "DCC.MightyDeedsOfArms": "Potężne Czyny Broni", @@ -540,6 +544,7 @@ "DCC.ResolveValueEmote": "Wyrzucono wartość {itemName}:
{pp} P. {ep} E. {gp} Z. {sp} S. {cp} M.", "DCC.Roll": "Rzut", "DCC.RollCritical": "Rzuć Krytyk", + "DCC.RollDeed": "Rzuć Czyn", "DCC.RolledAbilityEmote": "{actorName} rzucił {abilityInlineRollHTML} na swoją {abilityName}.", "DCC.AbilityCheckPenaltyNote": "

Jeśli kara kontrolna ma zastosowanie, suma wynosi {total}.

", "DCC.RolledCritEmote": "{actorName} rzucił {critInlineRollHTML} na Tabeli Krytyka {critTableName}{critResult}", @@ -639,6 +644,10 @@ "DCC.SettingManualCompendiumConfigurationHint": "Jeśli chcesz zbudować własne kompendium wyszukiwania zamiast używać Modułu Core Book - nie zalecane", "DCC.SettingMercurialMagicTable": "Tabela Merkurialnej Magii", "DCC.SettingMercurialMagicTableHint": "Tabela Rzutów do użycia dla efektów Merkurialnej Magii - musi być w pakiecie kompendium", + "DCC.SettingMightyDeedsEnabled": "Włącz Tabele Czynów", + "DCC.SettingMightyDeedsEnabledHint": "Udostępnia wybór Tabeli Czynów na kartach ataku, gdy kość czynu wojownika odniesie sukces (3 lub więcej). Domyślnie wyłączone.", + "DCC.SettingMightyDeedsTablesCompendium": "Kompendium Tabel Czynów", + "DCC.SettingMightyDeedsTablesCompendiumHint": "Kompendium, w którym wyszukiwane są tabele Potężnych Czynów. Tabele świata zawierające 'Deed' w nazwie są również dostępne na karcie czatu rzutu ataku", "DCC.SettingShowRollModifierByDefault": "Pokazuj okno Modyfikacji Rzutu domyślnie", "DCC.SettingShowRollModifierByDefaultHint": "Pokazuj okno Modyfikatora Rzutu bez konieczności kliku CMD/CTRL na elementach arkusza postaci", "DCC.SettingTurnUnholyTable": "Tabela Odwracania Nieczystych", diff --git a/module/__tests__/chat-and-hook-wiring.test.js b/module/__tests__/chat-and-hook-wiring.test.js index 98031210..03f6ae6e 100644 --- a/module/__tests__/chat-and-hook-wiring.test.js +++ b/module/__tests__/chat-and-hook-wiring.test.js @@ -25,7 +25,8 @@ vi.mock('../chat.js', () => ({ emoteDamageRoll: vi.fn(), emoteInitiativeRoll: vi.fn(), emoteSavingThrowRoll: vi.fn(), - emoteSkillCheckRoll: vi.fn() + emoteSkillCheckRoll: vi.fn(), + attachMightyDeedListeners: vi.fn() })) vi.mock('../parser.js', () => ({ @@ -201,6 +202,8 @@ describe('onRenderChatMessageHTML', () => { expect(chat.enforceMinimumDamage).toHaveBeenCalledWith(message, html) expect(SpellResult.processChatMessage).toHaveBeenCalledWith(message, html, {}) expect(TableResult.processChatMessage).toHaveBeenCalledWith(message, html, {}) + // Mighty Deed prompt listeners are attached after the emote/lookup passes (issue #319) + expect(chat.attachMightyDeedListeners).toHaveBeenCalledWith(message, html) }) test('forwards the dcc.ItemId flag onto a data-item-id attribute', async () => { diff --git a/module/__tests__/settings-table-hooks.test.js b/module/__tests__/settings-table-hooks.test.js index f98178f8..bdcd438e 100644 --- a/module/__tests__/settings-table-hooks.test.js +++ b/module/__tests__/settings-table-hooks.test.js @@ -10,6 +10,7 @@ import { SETTINGS_TABLE_HOOKS, onRegisterCriticalHitsPack, onRegisterDisapprovalPack, + onRegisterMightyDeedsPack, onRegisterLevelDataPack, onRegisterMercurialMagicTable, onSetDivineAidTable, @@ -207,6 +208,7 @@ describe('onSetTurnUnholyTable', () => { describe('SETTINGS_TABLE_HOOKS dispatch table', () => { test('routes each documented hook name to the corresponding handler', () => { expect(SETTINGS_TABLE_HOOKS['dcc.registerDisapprovalPack']).toBe(onRegisterDisapprovalPack) + expect(SETTINGS_TABLE_HOOKS['dcc.registerMightyDeedsPack']).toBe(onRegisterMightyDeedsPack) expect(SETTINGS_TABLE_HOOKS['dcc.registerCriticalHitsPack']).toBe(onRegisterCriticalHitsPack) expect(SETTINGS_TABLE_HOOKS['dcc.setDivineAidTable']).toBe(onSetDivineAidTable) expect(SETTINGS_TABLE_HOOKS['dcc.setFumbleTable']).toBe(onSetFumbleTable) @@ -217,12 +219,13 @@ describe('SETTINGS_TABLE_HOOKS dispatch table', () => { expect(SETTINGS_TABLE_HOOKS['dcc.setTurnUnholyTable']).toBe(onSetTurnUnholyTable) }) - test('covers exactly the nine documented hook names', () => { + test('covers exactly the ten documented hook names', () => { expect(Object.keys(SETTINGS_TABLE_HOOKS).sort()).toEqual([ 'dcc.registerCriticalHitsPack', 'dcc.registerDisapprovalPack', 'dcc.registerLevelDataPack', 'dcc.registerMercurialMagicTable', + 'dcc.registerMightyDeedsPack', 'dcc.setDivineAidTable', 'dcc.setFumbleTable', 'dcc.setLayOnHandsTable', @@ -254,8 +257,8 @@ describe('registerSettingsTableHooks', () => { } }) - test('registers exactly nine listeners — one per dispatch-table entry', () => { + test('registers exactly ten listeners — one per dispatch-table entry', () => { registerSettingsTableHooks() - expect(globalThis.Hooks.on).toHaveBeenCalledTimes(9) + expect(globalThis.Hooks.on).toHaveBeenCalledTimes(10) }) }) diff --git a/module/__tests__/table-loading.test.js b/module/__tests__/table-loading.test.js index e1082990..2dbd3dc3 100644 --- a/module/__tests__/table-loading.test.js +++ b/module/__tests__/table-loading.test.js @@ -400,6 +400,16 @@ describe('onImportAdventure', () => { describe('onCreateRollTable', () => { beforeEach(() => { globalThis.CONFIG.DCC.disapprovalTables = {} + globalThis.CONFIG.DCC.mightyDeedsTables = {} + }) + + test('adds the table to mightyDeedsTables when the name contains "Deed" (issue #319)', () => { + onCreateRollTable({ name: 'Mighty Deed: Disarm' }) + + expect(globalThis.CONFIG.DCC.mightyDeedsTables['Mighty Deed: Disarm']).toEqual({ + name: 'Mighty Deed: Disarm', + path: 'Mighty Deed: Disarm' + }) }) test('adds the table to disapprovalTables when the name contains "Disapproval"', () => { @@ -432,6 +442,7 @@ describe('onDeleteRollTable', () => { 'Cleric Disapproval': { name: 'Cleric Disapproval', path: 'Cleric Disapproval' }, 'Other Table': { name: 'Other Table', path: 'Other Table' } } + globalThis.CONFIG.DCC.mightyDeedsTables = {} onDeleteRollTable({ name: 'Cleric Disapproval' }) @@ -439,8 +450,22 @@ describe('onDeleteRollTable', () => { expect(globalThis.CONFIG.DCC.disapprovalTables['Other Table']).toBeDefined() }) + test('removes the table from mightyDeedsTables by name (issue #319)', () => { + globalThis.CONFIG.DCC.disapprovalTables = {} + globalThis.CONFIG.DCC.mightyDeedsTables = { + 'Mighty Deed': { name: 'Mighty Deed', path: 'Mighty Deed' }, + 'Other Deed': { name: 'Other Deed', path: 'Other Deed' } + } + + onDeleteRollTable({ name: 'Mighty Deed' }) + + expect(globalThis.CONFIG.DCC.mightyDeedsTables['Mighty Deed']).toBeUndefined() + expect(globalThis.CONFIG.DCC.mightyDeedsTables['Other Deed']).toBeDefined() + }) + test('is a no-op when the named table is not currently tracked', () => { globalThis.CONFIG.DCC.disapprovalTables = {} + globalThis.CONFIG.DCC.mightyDeedsTables = {} expect(() => onDeleteRollTable({ name: 'Not Present' })).not.toThrow() }) @@ -451,6 +476,7 @@ describe('onUpdateRollTable', () => { globalThis.CONFIG.DCC.disapprovalTables = { 'Cleric Disapproval': { name: 'Cleric Disapproval', path: 'Cleric Disapproval' } } + globalThis.CONFIG.DCC.mightyDeedsTables = {} globalThis.game.tables = Object.assign([], { getName: vi.fn() }) onUpdateRollTable({ name: 'Cleric Disapproval' }, { description: 'changed' }) @@ -463,6 +489,7 @@ describe('onUpdateRollTable', () => { 'Cleric Disapproval': { name: 'Cleric Disapproval', path: 'dcc-core.dcc-tables.Cleric Disapproval' }, 'Old World Name': { name: 'Old World Name', path: 'Old World Name' } } + globalThis.CONFIG.DCC.mightyDeedsTables = {} globalThis.game.tables = Object.assign( [{ name: 'New World Disapproval' }], { getName: vi.fn() } @@ -491,6 +518,7 @@ describe('onUpdateRollTable', () => { globalThis.CONFIG.DCC.disapprovalTables = { 'Stale Disapproval': { name: 'Stale Disapproval', path: 'Stale Disapproval' } } + globalThis.CONFIG.DCC.mightyDeedsTables = {} globalThis.game.tables = Object.assign( [{ name: 'Renamed Random Table' }], { getName: vi.fn() } @@ -504,6 +532,36 @@ describe('onUpdateRollTable', () => { expect(globalThis.CONFIG.DCC.disapprovalTables['Stale Disapproval']).toBeUndefined() expect(globalThis.CONFIG.DCC.disapprovalTables['Renamed Random Table']).toBeUndefined() }) + + test('preserves compendium deed entries and rebuilds the world half on rename (issue #319)', () => { + globalThis.CONFIG.DCC.disapprovalTables = {} + globalThis.CONFIG.DCC.mightyDeedsTables = { + 'Core Deed': { name: 'Core Deed', path: 'dcc-core-book.dcc-tables.Core Deed' }, + 'Old Deed Name': { name: 'Old Deed Name', path: 'Old Deed Name' } + } + globalThis.game.tables = Object.assign( + [{ name: 'New World Deed' }], + { getName: vi.fn() } + ) + + onUpdateRollTable( + { name: 'New World Deed' }, + { name: 'New World Deed' } + ) + + // Compendium deed entry survives (its path contains a dot) + expect(globalThis.CONFIG.DCC.mightyDeedsTables['Core Deed']).toEqual({ + name: 'Core Deed', + path: 'dcc-core-book.dcc-tables.Core Deed' + }) + // Stale world deed entry is gone after rebuild + expect(globalThis.CONFIG.DCC.mightyDeedsTables['Old Deed Name']).toBeUndefined() + // New world deed entry from game.tables walk is present + expect(globalThis.CONFIG.DCC.mightyDeedsTables['New World Deed']).toEqual({ + name: 'New World Deed', + path: 'New World Deed' + }) + }) }) describe('TABLE_LOADING_HOOKS dispatch table', () => { diff --git a/module/__tests__/utilities.test.js b/module/__tests__/utilities.test.js index df714db7..ed243e00 100644 --- a/module/__tests__/utilities.test.js +++ b/module/__tests__/utilities.test.js @@ -12,7 +12,8 @@ import { getCritTableResult, getFumbleTableResult, getFumbleTableNameFromCritTableName, - getNPCFumbleTableResult + getNPCFumbleTableResult, + getTableFromPath } from '../utilities.js' import { clearAllTableCaches, critTableDocCache, critTableLinkCache } from '../adapter/table-cache.mjs' @@ -523,6 +524,68 @@ describe('Utilities', () => { }) }) + describe('getTableFromPath', () => { + let mockPack + let mockTable + + beforeEach(() => { + mockTable = { + name: 'Deed: Trips and Throws', + getResultsForRoll: vi.fn() + } + + mockPack = { + index: [{ _id: 'deed-table-id', name: 'Deed: Trips and Throws' }], + getDocument: vi.fn().mockResolvedValue(mockTable) + } + + global.game = { + packs: { + get: vi.fn().mockReturnValue(mockPack) + }, + tables: { + getName: vi.fn().mockReturnValue(null) + } + } + }) + + it('returns null for an empty path', async () => { + expect(await getTableFromPath('')).toBeNull() + expect(await getTableFromPath(null)).toBeNull() + }) + + it('resolves a compendium path', async () => { + const result = await getTableFromPath('some-module.deed-tables.Deed: Trips and Throws') + expect(global.game.packs.get).toHaveBeenCalledWith('some-module.deed-tables') + expect(mockPack.getDocument).toHaveBeenCalledWith('deed-table-id') + expect(result).toBe(mockTable) + }) + + it('resolves a world table by name', async () => { + const worldTable = { name: 'Deed: Disarm' } + global.game.tables.getName.mockReturnValue(worldTable) + + const result = await getTableFromPath('Deed: Disarm') + expect(global.game.tables.getName).toHaveBeenCalledWith('Deed: Disarm') + expect(result).toBe(worldTable) + }) + + it('falls back to a world table when the pack is missing', async () => { + global.game.packs.get.mockReturnValue(null) + const worldTable = { name: 'some-module.deed-tables.Deed: Trips and Throws' } + global.game.tables.getName.mockReturnValue(worldTable) + + const result = await getTableFromPath('some-module.deed-tables.Deed: Trips and Throws') + expect(result).toBe(worldTable) + }) + + it('returns null when nothing matches', async () => { + global.game.packs.get.mockReturnValue(null) + const result = await getTableFromPath('Nonexistent Table') + expect(result).toBeNull() + }) + }) + describe('getFumbleTableResult', () => { let mockRoll let mockPack diff --git a/module/actor/rolls-weapon-mixin.mjs b/module/actor/rolls-weapon-mixin.mjs index 36de950c..dc65da61 100644 --- a/module/actor/rolls-weapon-mixin.mjs +++ b/module/actor/rolls-weapon-mixin.mjs @@ -1,4 +1,4 @@ -/* global game, Hooks, Roll, ChatMessage, ui, foundry */ +/* global CONFIG, game, Hooks, Roll, ChatMessage, ui, foundry */ import { ensurePlus, getCritTableResult, getCritTableLink, getFumbleTableResult, getNPCFumbleTableResult, getFumbleTableNameFromCritTableName, addDamageFlavorToRolls } from '../utilities.js' import { @@ -146,6 +146,14 @@ export const RollsWeaponMixin = (Base) => class extends Base { const deedDieRollResult = attackRollResult.deedDieRollResult const deedRollSuccess = attackRollResult.deedDieRollResult > 2 + // On a successful deed, offer a prompt to look up the deed die result on a Mighty Deed table (issue #319). + // Gated on the `mightyDeedsEnabled` world setting (off by default) so the prompt never appears unless a GM opts in. + let deedTables = [] + if (deedRollSuccess && game.settings.get('dcc', 'mightyDeedsEnabled')) { + deedTables = Object.values(CONFIG.DCC.mightyDeedsTables || {}) + .sort((a, b) => a.name.localeCompare(b.name)) + } + // Crit roll let critRollFormula = '' let critInlineRoll = '' @@ -272,6 +280,7 @@ export const RollsWeaponMixin = (Base) => class extends Base { deedDieRoll, deedDieRollResult, deedRollSuccess, + deedTables, fumbleInlineRoll, fumblePrompt, fumbleRoll, diff --git a/module/chat-and-hook-wiring.mjs b/module/chat-and-hook-wiring.mjs index 1b632722..ee9e3783 100644 --- a/module/chat-and-hook-wiring.mjs +++ b/module/chat-and-hook-wiring.mjs @@ -117,6 +117,9 @@ export async function onRenderChatMessageHTML (message, html, data) { // Process table result navigation AFTER emote/lookup functions have modified the HTML // This ensures event listeners are attached to the final DOM elements TableResult.processChatMessage(message, html, data) + + // Attach Mighty Deed table prompt listeners after the emote functions have modified the HTML (issue #319) + chat.attachMightyDeedListeners(message, html) } /** diff --git a/module/chat.js b/module/chat.js index 58f45fa1..1366804f 100644 --- a/module/chat.js +++ b/module/chat.js @@ -1,7 +1,7 @@ -/* global canvas, foundry, game, ui */ +/* global canvas, foundry, game, ui, ChatMessage */ // noinspection DuplicatedCode -import { getCritTableResult, getFumbleTableResult, getNPCFumbleTableResult, addDamageFlavorToRolls } from './utilities.js' +import { getCritTableResult, getFumbleTableResult, getNPCFumbleTableResult, getTableFromPath, addDamageFlavorToRolls } from './utilities.js' const { TextEditor } = foundry.applications.ux @@ -270,6 +270,16 @@ export const emoteAttackRoll = function (message, html) { deedDieHTML = `${message.system.deedDieRollResult}` } deedRollHTML = game.i18n.format('DCC.AttackRollDeedEmoteSegment', { deed: deedDieHTML }) + + // Re-add the Mighty Deed table prompt, since the emote replaces the card content + if (message.system.deedTables?.length) { + const options = message.system.deedTables.map(t => ``).join('') + deedRollHTML += ` +
+ + +
` + } } let crit = '' @@ -340,6 +350,70 @@ export const emoteAttackRoll = function (message, html) { } } +/** + * Attach listeners for the Mighty Deed table prompt on attack cards + * @param message + * @param html + */ +export const attachMightyDeedListeners = function (message, html) { + html.querySelectorAll('.roll-deed-table').forEach(el => { + el.addEventListener('click', _onRollMightyDeed.bind(message)) + }) +} + +/** + * Look up the deed die result on the selected Mighty Deed table and post the result to chat + * @param {Object} event The originating click event + */ +const _onRollMightyDeed = async function (event) { + const button = event.currentTarget + // One-shot: ignore repeat clicks and guard against a double-fire while the + // async table lookup is in flight. Re-enabled below only if the lookup fails. + if (button.disabled) { return } + const select = button.closest('.deed-table-prompt')?.querySelector('.deed-table-select') + const reEnable = () => { button.disabled = false; if (select) { select.disabled = false } } + button.disabled = true + if (select) { select.disabled = true } + + const container = button.closest('.deed-table-prompt') + if (!container) { reEnable(); return } + + const deedRoll = parseInt(container.getAttribute('data-deed-roll')) + const tablePath = container.querySelector('.deed-table-select')?.value + if (!tablePath || isNaN(deedRoll)) { reEnable(); return } + + const table = await getTableFromPath(tablePath) + if (!table) { + ui.notifications.warn(game.i18n.format('DCC.MightyDeedTableNotFound', { table: tablePath })) + reEnable() + return + } + + const result = table.getResultsForRoll(deedRoll)[0] + if (!result) { + ui.notifications.warn(game.i18n.localize('DCC.TableResultOutOfBounds')) + reEnable() + return + } + + const resultText = await TextEditor.enrichHTML(addDamageFlavorToRolls(result.description)) + const actor = game.actors.get(this.system?.actorId) + await ChatMessage.create({ + user: game.user.id, + speaker: ChatMessage.getSpeaker({ actor }), + flavor: game.i18n.format('DCC.MightyDeedRollFlavor', { table: table.name, roll: deedRoll }), + flags: { + 'dcc.isMightyDeed': true + }, + content: ` +
+
  1. +
    ${resultText}
    +
+
` + }) +} + /** * Change crit rolls into emotes * @param message diff --git a/module/config.js b/module/config.js index c332d57f..d47224a2 100644 --- a/module/config.js +++ b/module/config.js @@ -363,6 +363,7 @@ DCC.levelDataPacks = null DCC.fumbleTable = null DCC.layOnHandsTable = null DCC.mercurialMagicTable = null +DCC.mightyDeedsPacks = null // Per-class mercurial magic table registry, keyed by lowercase // `system.details.sheetClass` (e.g. `'wizard'`, `'elf'`, `'blaster'`, @@ -449,6 +450,9 @@ DCC.turnUnholyTable = null // List of available disapproval tables for the cleric sheet, generated from disapprovalPacks DCC.disapprovalTables = {} +// List of available Mighty Deed tables for the attack card deed prompt, generated from mightyDeedsPacks +DCC.mightyDeedsTables = {} + // Registry for skills that use a table lookup - maps skill name to config property // System defaults defined here, modules can register their own DCC.skillTables = { diff --git a/module/settings-table-hooks.mjs b/module/settings-table-hooks.mjs index 403faa6a..77b3ed4c 100644 --- a/module/settings-table-hooks.mjs +++ b/module/settings-table-hooks.mjs @@ -23,6 +23,13 @@ export function onRegisterDisapprovalPack (value, fromSystemSetting = false) { } } +export function onRegisterMightyDeedsPack (value, fromSystemSetting = false) { + const mightyDeedsPacks = CONFIG.DCC.mightyDeedsPacks + if (mightyDeedsPacks) { + mightyDeedsPacks.addPack(value, fromSystemSetting) + } +} + export function onRegisterCriticalHitsPack (value, fromSystemSetting = false) { const criticalHitPacks = CONFIG.DCC.criticalHitPacks if (criticalHitPacks) { @@ -97,6 +104,7 @@ export function onSetTurnUnholyTable (value, fromSystemSetting = false) { export const SETTINGS_TABLE_HOOKS = Object.freeze({ 'dcc.registerDisapprovalPack': onRegisterDisapprovalPack, + 'dcc.registerMightyDeedsPack': onRegisterMightyDeedsPack, 'dcc.registerCriticalHitsPack': onRegisterCriticalHitsPack, 'dcc.setDivineAidTable': onSetDivineAidTable, 'dcc.setFumbleTable': onSetFumbleTable, diff --git a/module/settings.js b/module/settings.js index be83387c..5bdfdcac 100644 --- a/module/settings.js +++ b/module/settings.js @@ -149,6 +149,36 @@ export const registerSystemSettings = async function () { } }) + /** + * Enable the Mighty Deed table prompt on attack cards (issue #319). + * Off by default — when disabled the attack card never offers the deed + * table dropdown, regardless of which deed tables exist in the world. + */ + game.settings.register('dcc', 'mightyDeedsEnabled', { + name: 'DCC.SettingMightyDeedsEnabled', + hint: 'DCC.SettingMightyDeedsEnabledHint', + scope: 'world', + type: Boolean, + default: false, + config: true + }) + + /** + * Compendium to look in for Mighty Deed tables + */ + game.settings.register('dcc', 'mightyDeedsCompendium', { + name: 'DCC.SettingMightyDeedsTablesCompendium', + hint: 'DCC.SettingMightyDeedsTablesCompendiumHint', + scope: 'world', + config: manualConfig, + default: '', + type: String, + choices: tableCompendiumNames, + onChange: value => { + Hooks.callAll('dcc.registerMightyDeedsPack', value, true) + } + }) + /** * Table to use for turn unholy */ diff --git a/module/table-loading.mjs b/module/table-loading.mjs index eaae34de..da6f43c3 100644 --- a/module/table-loading.mjs +++ b/module/table-loading.mjs @@ -28,6 +28,16 @@ function isDisapprovalTable (tableName) { return tableName.includes('Disapproval') || tableName.includes(disapprovalText) } +/** + * Module-private predicate for Mighty Deed tables (issue #319). Mirrors + * `isDisapprovalTable` — reads `game.i18n` per call so the localized + * "Deed" string reflects the active language at hook-fire time. + */ +function isMightyDeedsTable (tableName) { + const deedText = game.i18n.localize('DCC.Deed') + return tableName.includes('Deed') || tableName.includes(deedText) +} + /** * Set up compendium links for the equipment tab if dcc-core-book module * is active. Stores links in `CONFIG.DCC.coreBookCompendiumLinks`. @@ -110,6 +120,54 @@ export function registerTables () { CONFIG.DCC.disapprovalPacks._updateHook(CONFIG.DCC.disapprovalPacks) } + // Create manager for Mighty Deed table packs (issue #319). Mirrors the + // disapproval manager: pull tables from the configured compendium plus + // any world table whose name contains "Deed" (or the localized term). + // The attack card only surfaces these when the `mightyDeedsEnabled` + // world setting is on, but the registry stays populated regardless so + // the compendium picker and world-table tracking behave consistently. + CONFIG.DCC.mightyDeedsPacks = new TablePackManager({ + updateHook: async (manager) => { + // Clear Mighty Deed tables + CONFIG.DCC.mightyDeedsTables = {} + + // For each valid pack, update the list of Mighty Deed tables available on the attack card deed prompt + // Using table name as key to enable de-duplication + for (const packName of manager.packs) { + const pack = game.packs.get(packName) + if (pack) { + for (const value of pack.index.values()) { + // Use table name as key for de-duplication + CONFIG.DCC.mightyDeedsTables[value.name] = { + name: value.name, + path: `${packName}.${value.name}` + } + } + } + } + + // Add world tables to the Mighty Deed tables list if they contain "Deed" in their name + // World tables will overwrite compendium tables with the same name (preferred) + // If multiple world tables have the same name, the last one processed wins + for (const table of game.tables) { + if (isMightyDeedsTable(table.name)) { + // Use table name as key - this overwrites compendium tables with same name + CONFIG.DCC.mightyDeedsTables[table.name] = { + name: table.name, + path: table.name + } + } + } + } + }) + const mightyDeedsCompendium = game.settings.get('dcc', 'mightyDeedsCompendium') + if (mightyDeedsCompendium) { + CONFIG.DCC.mightyDeedsPacks.addPack(mightyDeedsCompendium, true) + } else { + // No compendium configured - still scan world tables for Mighty Deed tables + CONFIG.DCC.mightyDeedsPacks._updateHook(CONFIG.DCC.mightyDeedsPacks) + } + // Create manager for critical hit table packs and register the system setting CONFIG.DCC.criticalHitPacks = new TablePackManager() CONFIG.DCC.criticalHitPacks.addPack(game.settings.get('dcc', 'critsCompendium'), true) @@ -253,6 +311,14 @@ export function onCreateRollTable (table) { path: table.name } } + + // Add to Mighty Deed tables list if the name contains "Deed" (issue #319) + if (isMightyDeedsTable(table.name)) { + CONFIG.DCC.mightyDeedsTables[table.name] = { + name: table.name, + path: table.name + } + } } /** @@ -261,6 +327,7 @@ export function onCreateRollTable (table) { export function onDeleteRollTable (table) { // Use table name as key to find and delete delete CONFIG.DCC.disapprovalTables[table.name] + delete CONFIG.DCC.mightyDeedsTables[table.name] } /** @@ -292,6 +359,24 @@ export function onUpdateRollTable (table, changes) { } } } + + // Rebuild the Mighty Deed world tables list the same way (issue #319) + const deedCompendiumTables = {} + for (const [key, value] of Object.entries(CONFIG.DCC.mightyDeedsTables)) { + // Keep compendium tables (they have paths with dots like "pack.table") + if (value.path.includes('.')) { + deedCompendiumTables[key] = value + } + } + CONFIG.DCC.mightyDeedsTables = deedCompendiumTables + for (const worldTable of game.tables) { + if (isMightyDeedsTable(worldTable.name)) { + CONFIG.DCC.mightyDeedsTables[worldTable.name] = { + name: worldTable.name, + path: worldTable.name + } + } + } } } diff --git a/module/utilities.js b/module/utilities.js index 88207007..b2680a1b 100644 --- a/module/utilities.js +++ b/module/utilities.js @@ -213,6 +213,32 @@ function resolveCritTableLink (critTableSuffix) { return null } +/** + * Resolve a RollTable from a path string + * @param {string} tablePath - a world table name, or a compendium path like 'scope.pack-name.Table Name' + * @returns {Promise} - the RollTable document, or null if not found + */ +export async function getTableFromPath (tablePath) { + if (!tablePath) { return null } + + // Compendium paths have at least three components: scope, pack name, table name + const pathComponents = tablePath.split('.') + if (pathComponents.length >= 3) { + const packName = pathComponents.slice(0, 2).join('.') + const tableName = pathComponents.slice(2).join('.') + const pack = game.packs.get(packName) + if (pack) { + const entry = pack.index.find((entity) => entity.name === tableName) + if (entry) { + return pack.getDocument(entry._id) + } + } + } + + // Fall back to a world table by name + return game.tables.getName(tablePath) || null +} + /** * Draw a result from the fumble table * @param roll - roll instance to use diff --git a/styles/_chat.scss b/styles/_chat.scss index 4d389b09..c141da26 100644 --- a/styles/_chat.scss +++ b/styles/_chat.scss @@ -61,6 +61,37 @@ a.inline-roll { } } +.deed-table-prompt { + align-items: center; + display: flex; + gap: 5px; + + .deed-table-select, + .roll-deed-table { + background: var(--chat-background); + border: 1px solid var(--color-border-light-primary); + color: var(--chat-primary-color); + } + + .deed-table-select { + flex: 1; + min-width: 0; + } + + .roll-deed-table { + flex: 0 0 auto; + font-family: var(--system-label-font), serif; + line-height: 1.5; + padding: 0 8px; + width: auto; + } +} + +// The deed result card has no result-range navigation column, so let the text span the full width +.table-draw.deed-table-result .table-results .table-result { + grid-template-columns: auto; +} + .flavor-text { font-family: var(--system-label-font), serif; diff --git a/styles/dcc.css b/styles/dcc.css index 955c67fa..5e72f69f 100644 --- a/styles/dcc.css +++ b/styles/dcc.css @@ -714,6 +714,33 @@ a.inline-roll { font-variant-numeric: tabular-nums; } +.deed-table-prompt { + align-items: center; + display: flex; + gap: 5px; +} +.deed-table-prompt .deed-table-select, +.deed-table-prompt .roll-deed-table { + background: var(--chat-background); + border: 1px solid var(--color-border-light-primary); + color: var(--chat-primary-color); +} +.deed-table-prompt .deed-table-select { + flex: 1; + min-width: 0; +} +.deed-table-prompt .roll-deed-table { + flex: 0 0 auto; + font-family: var(--system-label-font), serif; + line-height: 1.5; + padding: 0 8px; + width: auto; +} + +.table-draw.deed-table-result .table-results .table-result { + grid-template-columns: auto; +} + .flavor-text { font-family: var(--system-label-font), serif; } diff --git a/styles/dcc.css.map b/styles/dcc.css.map index 05bbb649..c666736b 100644 --- a/styles/dcc.css.map +++ b/styles/dcc.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["_grid.scss","_base.scss","_journal.scss","_armor.scss","_chat.scss","_weapons.scss","_class-sheets.scss","_party-sheet.scss","_hit-points-dialog.scss","_ability-score-log.scss","_items.scss","_config-dialogs.scss","_skills.scss","_tabs.scss","_entity-link.scss","_dialogs.scss","_actor-sheet.scss","_effects.scss","_level-change-dialog.scss","_container-items.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;ACrOF;AACA;AACA;AACA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;AACA;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;;AAGF;EAEE;EACA;EACA;;AAGF;EAEE;EACA;;AAGF;EAEE;EACA;;AAGF;EAIE;EACA;EACA;EACA;EACA;EACA;;AAGF;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EAEE;;AAGF;EAIE;;AAGF;EAEE;;AAGF;EAEE;EACA;EACA;EACA;;AAGF;EAIE;EACA;EACA;;AAGF;EAEE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAGA;EACE;;AAIF;EACE;EACA;EACA;;;AC1XN;AACA;AAAA;AAEA;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAIA;AAAA;EAEE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMJ;EACE;EACA;;AAGF;EACE;EACA;;AAIF;AAAA;EAEE;EACA;;;AAQJ;AAAA;EACE;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;;;ACzGN;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AC/BN;AACA;AACA;AACA;EACE;EACA;EACA;;;AAGF;AAAA;AAAA;AAAA;EAIE;EACA;;;AAGF;AAAA;AAAA;EAGE;;;AAGF;EACE;;;AAGF;AACA;AACA;AAIA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;;AAIJ;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;;;AAIF;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAEA;EACE;;AAEA;EACE;EACA;;;AAMR;EACE;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;AACA;AACA;AAEE;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;EAME;;AAMR;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAKN;AACA;AACA;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;AAAA;EAEE;EACA;;;ACvNN;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAIA;EACA;;AAEA;EACE;;AAGF;EACE;;;AChHV;AACA;AACA;AAGE;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;;AAKN;EACE;EACA;EACA;EACA;EACA;;;AAGF;AACA;AAAA;AAGA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;;AASF;EACE;EACA;EACA;EACA;;AAEA;AAAA;EAEE;EACA;;;AAOV;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;;AAEA;AAAA;EAEE;EACA;;;AAOV;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;ACpIF;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EAKA;;AAIJ;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;;AAKN;EACE;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;AAAA;EAEE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AC7HN;AACA;AACA;AAGE;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;;;ACpCJ;AACA;AACA;AAGE;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMJ;AAAA;EAEE;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAKN;AACA;AACA;AAGE;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;;AAMR;AACA;AACA;AAGE;EACE;;AAGF;AAAA;EAEE;;AAGF;EACE;EACA;EACA;;;AC7MJ;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;AACA;AACA;AAGE;AAAA;EAEE;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AC1QN;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;;AC7EN;AACA;AAAA;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;AAAA;AAAA;AAGA;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;ACtDF;AACA;AACA;AAGE;EACE;EACA;EACA;EACA;;AALJ;AAQE;;AACA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAOV;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;;AAGF;EACE;;AAIF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;AAAA;AAAA;AAAA;EAIE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AClNJ;AACA;AACA;AAEE;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;ACXJ;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EAEA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;;;AAIJ;AACA;AACA;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAKN;AACA;AACA;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;AAAA;EAGE;EACA;;AAEA;AAAA;AAAA;EACE;;AAIJ;EACE;EACA;;AAKN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AC3VR;AACA;AAAA;AAIE;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAKA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;AAAA;EAEE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAOV;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMR;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EAKA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMR;EACE;EACA;EACA;;;AAMJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EAKA;;AAKA;EACE;;AAGF;EACE;;AAIJ;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAad;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAMF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;;AC5pBJ;AAEE;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGE;EACE;;AAKN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAKN;AAGI;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;;AC7JN;AAEE;EACE;EACA;EACA;;;ACLJ;AAGI;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAMJ;EACE;EACA;EACA;EACA;;AAIA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;;AAIJ;EACE;EACA;EACA;EACA","file":"dcc.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["_grid.scss","_base.scss","_journal.scss","_armor.scss","_chat.scss","_weapons.scss","_class-sheets.scss","_party-sheet.scss","_hit-points-dialog.scss","_ability-score-log.scss","_items.scss","_config-dialogs.scss","_skills.scss","_tabs.scss","_entity-link.scss","_dialogs.scss","_actor-sheet.scss","_effects.scss","_level-change-dialog.scss","_container-items.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;ACrOF;AACA;AACA;AACA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;AACA;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;;AAGF;EAEE;EACA;EACA;;AAGF;EAEE;EACA;;AAGF;EAEE;EACA;;AAGF;EAIE;EACA;EACA;EACA;EACA;EACA;;AAGF;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EAEE;;AAGF;EAIE;;AAGF;EAEE;;AAGF;EAEE;EACA;EACA;EACA;;AAGF;EAIE;EACA;EACA;;AAGF;EAEE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAGA;EACE;;AAIF;EACE;EACA;EACA;;;AC1XN;AACA;AAAA;AAEA;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAIA;AAAA;EAEE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMJ;EACE;EACA;;AAGF;EACE;EACA;;AAIF;AAAA;EAEE;EACA;;;AAQJ;AAAA;EACE;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;;;ACzGN;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AC/BN;AACA;AACA;AACA;EACE;EACA;EACA;;;AAGF;AAAA;AAAA;AAAA;EAIE;EACA;;;AAGF;AAAA;AAAA;EAGE;;;AAGF;EACE;;;AAGF;AACA;AACA;AAIA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;;AAEA;AAAA;EAEE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAKJ;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;;;AAIF;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAEA;EACE;;AAEA;EACE;EACA;;;AAMR;EACE;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;AACA;AACA;AAEE;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;EAME;;AAMR;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAKN;AACA;AACA;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;AAAA;EAEE;EACA;;;ACtPN;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAIA;EACA;;AAEA;EACE;;AAGF;EACE;;;AChHV;AACA;AACA;AAGE;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;;AAKN;EACE;EACA;EACA;EACA;EACA;;;AAGF;AACA;AAAA;AAGA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;;AASF;EACE;EACA;EACA;EACA;;AAEA;AAAA;EAEE;EACA;;;AAOV;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;;AAEA;AAAA;EAEE;EACA;;;AAOV;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;ACpIF;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EAKA;;AAIJ;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;;AAKN;EACE;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;AAAA;EAEE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AC7HN;AACA;AACA;AAGE;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;;;ACpCJ;AACA;AACA;AAGE;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMJ;AAAA;EAEE;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAKN;AACA;AACA;AAGE;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;;AAMR;AACA;AACA;AAGE;EACE;;AAGF;AAAA;EAEE;;AAGF;EACE;EACA;EACA;;;AC7MJ;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;AACA;AACA;AAGE;AAAA;EAEE;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AC1QN;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;;AC7EN;AACA;AAAA;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;AAAA;AAAA;AAGA;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;ACtDF;AACA;AACA;AAGE;EACE;EACA;EACA;EACA;;AALJ;AAQE;;AACA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAOV;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;;AAGF;EACE;;AAIF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;AAAA;AAAA;AAAA;EAIE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AClNJ;AACA;AACA;AAEE;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;ACXJ;AACA;AACA;AAEA;EACE;EACA;EACA;EACA;EAEA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;;;AAIJ;AACA;AACA;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAKN;AACA;AACA;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;AAAA;EAGE;EACA;;AAEA;AAAA;AAAA;EACE;;AAIJ;EACE;EACA;;AAKN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AC3VR;AACA;AAAA;AAIE;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAKA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;AAAA;EAEE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAOV;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMR;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EAKA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMR;EACE;EACA;EACA;;;AAMJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EAKA;;AAKA;EACE;;AAGF;EACE;;AAIJ;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAad;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAMF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;;AC5pBJ;AAEE;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGE;EACE;;AAKN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAKN;AAGI;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;;AC7JN;AAEE;EACE;EACA;EACA;;;ACLJ;AAGI;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAMJ;EACE;EACA;EACA;EACA;;AAIA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;;AAIJ;EACE;EACA;EACA;EACA","file":"dcc.css"} \ No newline at end of file diff --git a/templates/chat-card-attack-result.html b/templates/chat-card-attack-result.html index a3134a4b..0eddef58 100644 --- a/templates/chat-card-attack-result.html +++ b/templates/chat-card-attack-result.html @@ -4,6 +4,16 @@ {{localize "DCC.DeedDie"}}: {{{message.system.deedDieRollResult}}} {{/if}} +{{#if message.system.deedTables.length}} +
+ + +
+{{/if}} {{#if message.system.damageInlineRoll}}
{{message.system.damagePrompt}}: {{{message.system.damageInlineRoll}}}
{{/if}}