From a78ef1f70c5e7bd9a050eadd942bbe7ff90a4f38 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:26:37 +0100 Subject: [PATCH] [Feature] RollTable Improvements (#1552) * Initial - Branch Test * reorganized path for better usage * something to mess with * registration things * . * root-template error * pushing in for the day * hook? * help? * . * implementation initial * updated comment * overcomplicated it * . * Added Formula select to view mode * . * Prettied up roll-results template * Removed SRD table descriptions * Improved draw result description css * Fallback for default dark dice * . --------- Co-authored-by: Nikhil Nagarajan --- daggerheart.mjs | 11 +- lang/en.json | 6 + module/applications/sheets/_module.mjs | 1 + .../sheets/rollTables/_module.mjs | 1 + .../sheets/rollTables/rollTable.mjs | 191 ++++++++++++++++++ module/data/_module.mjs | 1 + module/data/rollTable.mjs | 38 ++++ module/documents/_module.mjs | 1 + module/documents/rollTable.mjs | 122 +++++++++++ .../tables_Consumables_tF04P02yVN1YDVel.json | 25 ++- .../tables_Loot_S61Shlt2I5CbLRjz.json | 25 ++- ...s_Random_Objectives_I5L1dlgxXTNrCCkL.json} | 19 +- styles/less/global/chat.less | 36 ++++ styles/less/sheets/index.less | 1 + styles/less/sheets/rollTables/sheet.less | 29 +++ templates/sheets/rollTable/header.hbs | 20 ++ templates/sheets/rollTable/results.hbs | 55 +++++ templates/sheets/rollTable/sheet.hbs | 49 +++++ templates/sheets/rollTable/summary.hbs | 22 ++ templates/ui/chat/table-result.hbs | 17 ++ 20 files changed, 660 insertions(+), 10 deletions(-) create mode 100644 module/applications/sheets/rollTables/_module.mjs create mode 100644 module/applications/sheets/rollTables/rollTable.mjs create mode 100644 module/data/rollTable.mjs create mode 100644 module/documents/rollTable.mjs rename src/packs/rolltables/{tables_Table_of_Random_Objectives_I5L1dlgxXTNrCCkL.json => tables_Random_Objectives_I5L1dlgxXTNrCCkL.json} (95%) create mode 100644 styles/less/sheets/rollTables/sheet.less create mode 100644 templates/sheets/rollTable/header.hbs create mode 100644 templates/sheets/rollTable/results.hbs create mode 100644 templates/sheets/rollTable/sheet.hbs create mode 100644 templates/sheets/rollTable/summary.hbs create mode 100644 templates/ui/chat/table-result.hbs diff --git a/daggerheart.mjs b/daggerheart.mjs index 77e9831e..7ae01590 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -58,6 +58,9 @@ CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer; CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate; +CONFIG.RollTable.documentClass = documents.DhRollTable; +CONFIG.RollTable.resultTemplate = 'systems/daggerheart/templates/ui/chat/table-result.hbs'; + CONFIG.Scene.documentClass = documents.DhScene; CONFIG.Token.documentClass = documents.DhToken; @@ -105,7 +108,7 @@ Hooks.once('init', () => { type: game.i18n.localize(typePath) }); - const { Items, Actors } = foundry.documents.collections; + const { Items, Actors, RollTables } = foundry.documents.collections; Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2); Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, { types: ['ancestry'], @@ -190,6 +193,12 @@ Hooks.once('init', () => { label: sheetLabel('TYPES.Actor.party') }); + RollTables.unregisterSheet('core', foundry.applications.sheets.RollTableSheet); + RollTables.registerSheet(SYSTEM.id, applications.sheets.rollTables.RollTableSheet, { + types: ['base'], + makeDefault: true + }); + DocumentSheetConfig.unregisterSheet( CONFIG.ActiveEffect.documentClass, 'core', diff --git a/lang/en.json b/lang/en.json index 22d4ae30..4d6815c3 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2383,6 +2383,12 @@ "secondaryWeapon": "Secondary Weapon" } }, + "ROLLTABLES": { + "FIELDS": { + "formulaName": { "label": "Formula Name" } + }, + "formula": "Formula" + }, "SETTINGS": { "Appearance": { "FIELDS": { diff --git a/module/applications/sheets/_module.mjs b/module/applications/sheets/_module.mjs index c503e054..390267d5 100644 --- a/module/applications/sheets/_module.mjs +++ b/module/applications/sheets/_module.mjs @@ -1,3 +1,4 @@ export * as actors from './actors/_module.mjs'; export * as api from './api/_modules.mjs'; export * as items from './items/_module.mjs'; +export * as rollTables from './rollTables/_module.mjs'; diff --git a/module/applications/sheets/rollTables/_module.mjs b/module/applications/sheets/rollTables/_module.mjs new file mode 100644 index 00000000..73067b64 --- /dev/null +++ b/module/applications/sheets/rollTables/_module.mjs @@ -0,0 +1 @@ +export { default as RollTableSheet } from './rollTable.mjs'; diff --git a/module/applications/sheets/rollTables/rollTable.mjs b/module/applications/sheets/rollTables/rollTable.mjs new file mode 100644 index 00000000..9ead6814 --- /dev/null +++ b/module/applications/sheets/rollTables/rollTable.mjs @@ -0,0 +1,191 @@ +export default class DhRollTableSheet extends foundry.applications.sheets.RollTableSheet { + static DEFAULT_OPTIONS = { + ...super.DEFAULT_OPTIONS, + actions: { + changeMode: DhRollTableSheet.#onChangeMode, + drawResult: DhRollTableSheet.#onDrawResult, + resetResults: DhRollTableSheet.#onResetResults, + addFormula: DhRollTableSheet.#addFormula, + removeFormula: DhRollTableSheet.#removeFormula + } + }; + + static buildParts() { + const { footer, header, sheet, results, ...parts } = super.PARTS; + return { + sheet: { + ...sheet, + template: 'systems/daggerheart/templates/sheets/rollTable/sheet.hbs' + }, + header: { template: 'systems/daggerheart/templates/sheets/rollTable/header.hbs' }, + ...parts, + results: { + template: 'systems/daggerheart/templates/sheets/rollTable/results.hbs', + templates: ['templates/sheets/roll-table/result-details.hbs'], + scrollable: ['table[data-results] tbody'] + }, + summary: { template: 'systems/daggerheart/templates/sheets/rollTable/summary.hbs' }, + footer + }; + } + + static PARTS = DhRollTableSheet.buildParts(); + + async _preRender(context, options) { + await super._preRender(context, options); + + if (!options.internalRefresh) + this.daggerheartFlag = new game.system.api.data.DhRollTable(this.document.flags.daggerheart); + } + + /* root PART has a blank element on _attachPartListeners, so it cannot be used to set the eventListeners for the view mode */ + async _onRender(context, options) { + super._onRender(context, options); + + for (const element of this.element.querySelectorAll('.system-update-field')) + element.addEventListener('change', this.updateSystemField.bind(this)); + } + + async _preparePartContext(partId, context, options) { + context = await super._preparePartContext(partId, context, options); + + switch (partId) { + case 'sheet': + context.altFormula = this.daggerheartFlag.altFormula; + context.usesAltFormula = Object.keys(this.daggerheartFlag.altFormula).length > 0; + context.altFormulaOptions = { + '': { name: this.daggerheartFlag.formulaName }, + ...this.daggerheartFlag.altFormula + }; + context.activeAltFormula = this.daggerheartFlag.activeAltFormula; + context.selectedFormula = this.daggerheartFlag.getActiveFormula(this.document.formula); + context.results = this.getExtendedResults(context.results); + break; + case 'header': + context.altFormula = this.daggerheartFlag.altFormula; + context.usesAltFormula = Object.keys(this.daggerheartFlag.altFormula).length > 0; + context.altFormulaOptions = { + '': { name: this.daggerheartFlag.formulaName }, + ...this.daggerheartFlag.altFormula + }; + context.activeAltFormula = this.daggerheartFlag.activeAltFormula; + break; + case 'summary': + context.systemFields = this.daggerheartFlag.schema.fields; + context.altFormula = this.daggerheartFlag.altFormula; + context.formulaName = this.daggerheartFlag.formulaName; + break; + case 'results': + context.results = this.getExtendedResults(context.results); + break; + } + + return context; + } + + getExtendedResults(results) { + const bodyDarkMode = document.body.classList.contains('theme-dark'); + const elementLightMode = this.element.classList.contains('theme-light'); + const elementDarkMode = this.element.classList.contains('theme-dark'); + const isDarkMode = elementDarkMode || (!elementLightMode && bodyDarkMode); + + return results.map(x => ({ + ...x, + displayImg: isDarkMode && x.img === 'icons/svg/d20-black.svg' ? 'icons/svg/d20.svg' : x.img + })); + } + + /* -------------------------------------------- */ + /* Flag SystemData update methods */ + /* -------------------------------------------- */ + + async updateSystemField(event) { + const { dataset, value } = event.target; + await this.daggerheartFlag.updateSource({ [dataset.path]: value }); + this.render({ internalRefresh: true }); + } + + getSystemFlagUpdate() { + const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce( + (acc, formulaKey) => { + if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[`-=${formulaKey}`] = null; + + return acc; + }, + { altFormula: {} } + ); + + return { ['flags.daggerheart']: foundry.utils.mergeObject(this.daggerheartFlag.toObject(), deleteUpdate) }; + } + + static async #addFormula() { + await this.daggerheartFlag.updateSource({ + [`altFormula.${foundry.utils.randomID()}`]: game.system.api.data.DhRollTable.getDefaultFormula() + }); + this.render({ internalRefresh: true }); + } + + static async #removeFormula(_event, target) { + await this.daggerheartFlag.updateSource({ + [`altFormula.-=${target.dataset.key}`]: null + }); + this.render({ internalRefresh: true }); + } + + /* -------------------------------------------- */ + /* Extended RollTable methods */ + /* -------------------------------------------- */ + + /** + * Alternate between view and edit modes. + * @this {RollTableSheet} + * @type {ApplicationClickAction} + */ + static async #onChangeMode() { + this.mode = this.isEditMode ? 'view' : 'edit'; + await this.document.update(this.getSystemFlagUpdate()); + await this.render({ internalRefresh: true }); + } + + /** @inheritdoc */ + async _processSubmitData(event, form, submitData, options) { + /* RollTable sends an empty dummy event when swapping from view/edit first time */ + if (Object.keys(submitData).length) { + if (!submitData.flags) submitData.flags = { daggerheart: {} }; + submitData.flags.daggerheart = this.getSystemFlagUpdate(); + } + + super._processSubmitData(event, form, submitData, options); + } + + /** @inheritdoc */ + static async #onResetResults() { + await this.document.update(this.getSystemFlagUpdate()); + await this.document.resetResults(); + } + + /** + * Roll and draw a TableResult. + * @this {RollTableSheet} + * @type {ApplicationClickAction} + */ + static async #onDrawResult(_event, button) { + if (this.form) await this.submit({ operation: { render: false } }); + button.disabled = true; + const table = this.document; + + await this.document.update(this.getSystemFlagUpdate()); + + /* Sending in the currently selectd activeFormula to table.roll to use as the formula */ + const selectedFormula = this.daggerheartFlag.getActiveFormula(this.document.formula); + const tableRoll = await table.roll({ selectedFormula }); + const draws = table.getResultsForRoll(tableRoll.roll.total); + if (draws.length > 0) { + if (game.settings.get('core', 'animateRollTable')) await this._animateRoll(draws); + await table.draw(tableRoll); + } + + // Reenable the button if drawing with replacement since the draw won't trigger a sheet re-render + if (table.replacement) button.disabled = false; + } +} diff --git a/module/data/_module.mjs b/module/data/_module.mjs index 7ad20808..f7e25a4e 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -1,6 +1,7 @@ export { default as DhCombat } from './combat.mjs'; export { default as DhCombatant } from './combatant.mjs'; export { default as DhTagTeamRoll } from './tagTeamRoll.mjs'; +export { default as DhRollTable } from './rollTable.mjs'; export { default as RegisteredTriggers } from './registeredTriggers.mjs'; export * as countdowns from './countdowns.mjs'; diff --git a/module/data/rollTable.mjs b/module/data/rollTable.mjs new file mode 100644 index 00000000..78f7e6dd --- /dev/null +++ b/module/data/rollTable.mjs @@ -0,0 +1,38 @@ +import FormulaField from './fields/formulaField.mjs'; + +//Extra definitions for RollTable +export default class DhRollTable extends foundry.abstract.TypeDataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + formulaName: new fields.StringField({ + required: true, + nullable: false, + initial: 'Roll Formula', + label: 'DAGGERHEART.ROLLTABLES.FIELDS.formulaName.label' + }), + altFormula: new fields.TypedObjectField( + new fields.SchemaField({ + name: new fields.StringField({ + required: true, + nullable: false, + initial: 'Roll Formula', + label: 'DAGGERHEART.ROLLTABLES.FIELDS.formulaName.label' + }), + formula: new FormulaField({ label: 'Formula Roll', initial: '1d20' }) + }) + ), + activeAltFormula: new fields.StringField({ nullable: true, initial: null }) + }; + } + + getActiveFormula(baseFormula) { + return this.activeAltFormula ? (this.altFormula[this.activeAltFormula]?.formula ?? baseFormula) : baseFormula; + } + + static getDefaultFormula = () => ({ + name: game.i18n.localize('Roll Formula'), + formula: '1d20' + }); +} diff --git a/module/documents/_module.mjs b/module/documents/_module.mjs index 8073cfe1..b9cfd3f2 100644 --- a/module/documents/_module.mjs +++ b/module/documents/_module.mjs @@ -4,6 +4,7 @@ export { default as DhpCombat } from './combat.mjs'; export { default as DHCombatant } from './combatant.mjs'; export { default as DhActiveEffect } from './activeEffect.mjs'; export { default as DhChatMessage } from './chatMessage.mjs'; +export { default as DhRollTable } from './rollTable.mjs'; export { default as DhScene } from './scene.mjs'; export { default as DhToken } from './token.mjs'; export { default as DhTooltipManager } from './tooltipManager.mjs'; diff --git a/module/documents/rollTable.mjs b/module/documents/rollTable.mjs new file mode 100644 index 00000000..50b8fe63 --- /dev/null +++ b/module/documents/rollTable.mjs @@ -0,0 +1,122 @@ +export default class DhRollTable extends foundry.documents.RollTable { + async roll({ selectedFormula, roll, recursive = true, _depth = 0 } = {}) { + // Prevent excessive recursion + if (_depth > 5) { + throw new Error(`Maximum recursion depth exceeded when attempting to draw from RollTable ${this.id}`); + } + + const formula = selectedFormula ?? this.formula; + + // If there is no formula, automatically calculate an even distribution + if (!this.formula) { + await this.normalize(); + } + + // Reference the provided roll formula + roll = roll instanceof Roll ? roll : Roll.create(formula); + let results = []; + + // Ensure that at least one non-drawn result remains + const available = this.results.filter(r => !r.drawn); + if (!available.length) { + ui.notifications.warn(game.i18n.localize('TABLE.NoAvailableResults')); + return { roll, results }; + } + + // Ensure that results are available within the minimum/maximum range + const minRoll = (await roll.reroll({ minimize: true })).total; + const maxRoll = (await roll.reroll({ maximize: true })).total; + const availableRange = available.reduce( + (range, result) => { + const r = result.range; + if (!range[0] || r[0] < range[0]) range[0] = r[0]; + if (!range[1] || r[1] > range[1]) range[1] = r[1]; + return range; + }, + [null, null] + ); + if (availableRange[0] > maxRoll || availableRange[1] < minRoll) { + ui.notifications.warn('No results can possibly be drawn from this table and formula.'); + return { roll, results }; + } + + // Continue rolling until one or more results are recovered + let iter = 0; + while (!results.length) { + if (iter >= 10000) { + ui.notifications.error( + `Failed to draw an available entry from Table ${this.name}, maximum iteration reached` + ); + break; + } + roll = await roll.reroll(); + results = this.getResultsForRoll(roll.total); + iter++; + } + + // Draw results recursively from any inner Roll Tables + if (recursive) { + const inner = []; + for (const result of results) { + const { type, documentUuid } = result; + const documentName = foundry.utils.parseUuid(documentUuid)?.type; + if (type === 'document' && documentName === 'RollTable') { + const innerTable = await fromUuid(documentUuid); + if (innerTable) { + const innerRoll = await innerTable.roll({ _depth: _depth + 1 }); + inner.push(...innerRoll.results); + } + } else inner.push(result); + } + results = inner; + } + + // Return the Roll and the results + return { roll, results }; + } + + async toMessage(results, { roll, messageData = {}, messageOptions = {} } = {}) { + messageOptions.rollMode ??= game.settings.get('core', 'rollMode'); + + // Construct chat data + messageData = foundry.utils.mergeObject( + { + author: game.user.id, + speaker: foundry.documents.ChatMessage.implementation.getSpeaker(), + rolls: [], + sound: roll ? CONFIG.sounds.dice : null, + flags: { 'core.RollTable': this.id } + }, + messageData + ); + if (roll) messageData.rolls.push(roll); + + // Render the chat card which combines the dice roll with the drawn results + const detailsPromises = await Promise.allSettled(results.map(r => r.getHTML())); + const flavorKey = `TABLE.DrawFlavor${results.length > 1 ? 'Plural' : ''}`; + const flavor = game.i18n.format(flavorKey, { + number: results.length, + name: foundry.utils.escapeHTML(this.name) + }); + messageData.content = await foundry.applications.handlebars.renderTemplate(CONFIG.RollTable.resultTemplate, { + description: await TextEditor.implementation.enrichHTML(this.description, { + documents: true, + secrets: this.isOwner + }), + flavor: flavor, + results: results.map((result, i) => { + const r = result.toObject(false); + r.details = detailsPromises[i].value ?? ''; + const useTableIcon = + result.icon === CONFIG.RollTable.resultIcon && this.img !== this.constructor.DEFAULT_ICON; + r.icon = useTableIcon ? this.img : result.icon; + return r; + }), + rollHTML: this.displayRoll && roll ? await roll.render() : null, + table: this + }); + + // Create the chat message + return foundry.documents.ChatMessage.implementation.create(messageData, messageOptions); + } +} diff --git a/src/packs/rolltables/tables_Consumables_tF04P02yVN1YDVel.json b/src/packs/rolltables/tables_Consumables_tF04P02yVN1YDVel.json index c2413ec3..c3f5ffdc 100644 --- a/src/packs/rolltables/tables_Consumables_tF04P02yVN1YDVel.json +++ b/src/packs/rolltables/tables_Consumables_tF04P02yVN1YDVel.json @@ -1,7 +1,7 @@ { "name": "Consumables", "img": "icons/consumables/potions/bottle-corked-red.webp", - "description": "

To generate a random consumable, choose a rarity, roll the designated dice, and match the total to the item in the table:

", + "description": "", "results": [ { "type": "document", @@ -1511,8 +1511,27 @@ "default": 0, "Bgvu4A6AMkRFOTGR": 3 }, - "flags": {}, - "formula": "1d60", + "flags": { + "daggerheart": { + "activeAltFormula": "", + "formulaName": "Common", + "altFormula": { + "uoUn5fRTUkyg6U2G": { + "name": "Uncommon", + "formula": "3d12" + }, + "FGxM2yoxUUUd9Eov": { + "name": "Rare", + "formula": "4d12" + }, + "HZ2hRBxu0k8IW0jC": { + "name": "Legendary", + "formula": "5d12" + } + } + } + }, + "formula": "2d12", "_id": "tF04P02yVN1YDVel", "sort": 300000, "_key": "!tables!tF04P02yVN1YDVel" diff --git a/src/packs/rolltables/tables_Loot_S61Shlt2I5CbLRjz.json b/src/packs/rolltables/tables_Loot_S61Shlt2I5CbLRjz.json index 9517eadd..2151ae81 100644 --- a/src/packs/rolltables/tables_Loot_S61Shlt2I5CbLRjz.json +++ b/src/packs/rolltables/tables_Loot_S61Shlt2I5CbLRjz.json @@ -1,7 +1,7 @@ { "name": "Loot", "img": "icons/commodities/treasure/brooch-gold-ruby.webp", - "description": "

To generate a random item, choose a rarity, roll the designated dice, and match the total to the item in the table:

", + "description": "", "results": [ { "type": "document", @@ -1511,8 +1511,27 @@ "default": 0, "Bgvu4A6AMkRFOTGR": 3 }, - "flags": {}, - "formula": "1d60", + "flags": { + "daggerheart": { + "activeAltFormula": "", + "formulaName": "Common", + "altFormula": { + "hJJtajaMk14bYM4X": { + "name": "Uncommon", + "formula": "3d12" + }, + "yDVeXdKpG7LzjHWa": { + "name": "Rare", + "formula": "4d12" + }, + "qPHNIuUgWAHauI6V": { + "name": "Legendary", + "formula": "5d12" + } + } + } + }, + "formula": "2d12", "_id": "S61Shlt2I5CbLRjz", "sort": 200000, "_key": "!tables!S61Shlt2I5CbLRjz" diff --git a/src/packs/rolltables/tables_Table_of_Random_Objectives_I5L1dlgxXTNrCCkL.json b/src/packs/rolltables/tables_Random_Objectives_I5L1dlgxXTNrCCkL.json similarity index 95% rename from src/packs/rolltables/tables_Table_of_Random_Objectives_I5L1dlgxXTNrCCkL.json rename to src/packs/rolltables/tables_Random_Objectives_I5L1dlgxXTNrCCkL.json index b10127e7..9165f9d7 100644 --- a/src/packs/rolltables/tables_Table_of_Random_Objectives_I5L1dlgxXTNrCCkL.json +++ b/src/packs/rolltables/tables_Random_Objectives_I5L1dlgxXTNrCCkL.json @@ -1,7 +1,7 @@ { - "name": "Table of Random Objectives", + "name": "Random Objectives", "img": "icons/sundries/documents/document-torn-diagram-tan.webp", - "description": "

Layering Goals Other than Attrition into Combat

", + "description": "", "results": [ { "type": "text", @@ -311,7 +311,20 @@ "default": 0, "Bgvu4A6AMkRFOTGR": 3 }, - "flags": {}, + "flags": { + "daggerheart": { + "formulaName": "Roll Formula", + "altFormula": {}, + "activeAltFormula": null, + "flags": { + "daggerheart": { + "formulaName": "Roll Formula", + "altFormula": {}, + "activeAltFormula": null + } + } + } + }, "formula": "1d12", "_id": "I5L1dlgxXTNrCCkL", "sort": 400000, diff --git a/styles/less/global/chat.less b/styles/less/global/chat.less index dc671e44..b9478ea4 100644 --- a/styles/less/global/chat.less +++ b/styles/less/global/chat.less @@ -15,6 +15,14 @@ .message-header .message-header-main .message-sub-header-container h4 { color: @dark-blue; } + + .message-content { + .table-draw { + .table-description { + color: @dark; + } + } + } } } @@ -83,6 +91,7 @@ .message-content { padding-bottom: 8px; + .flavor-text { font-size: var(--font-size-12); line-height: 20px; @@ -90,6 +99,33 @@ text-align: center; display: block; } + + .table-draw { + .table-flavor { + padding-top: 5px; + padding-bottom: 0.5rem; + font-size: var(--font-size-12); + } + + .table-description { + color: @beige; + font-style: italic; + + &.flavor-spaced { + padding-top: 0; + } + } + + .table-results { + .description { + flex-basis: min-content; + + > p:first-of-type { + margin-top: 0; + } + } + } + } } } } diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index 216cda33..1bdb451a 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -40,4 +40,5 @@ @import './items/heritage.less'; @import './items/item-sheet-shared.less'; +@import './rollTables/sheet.less'; @import './actions/actions.less'; diff --git a/styles/less/sheets/rollTables/sheet.less b/styles/less/sheets/rollTables/sheet.less new file mode 100644 index 00000000..a7c05455 --- /dev/null +++ b/styles/less/sheets/rollTables/sheet.less @@ -0,0 +1,29 @@ +.application.sheet.roll-table-sheet { + .formulas-section { + legend { + margin-left: auto; + margin-right: auto; + } + + .formulas-container { + display: grid; + grid-template-columns: 1fr 1fr 40px; + gap: 10px; + text-align: center; + + .formula-button { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + } + } + } + + .roll-table-view-formula-container { + width: fit-content; + display: flex; + align-items: center; + gap: 4px; + } +} diff --git a/templates/sheets/rollTable/header.hbs b/templates/sheets/rollTable/header.hbs new file mode 100644 index 00000000..6670e92e --- /dev/null +++ b/templates/sheets/rollTable/header.hbs @@ -0,0 +1,20 @@ +
+ {{localize + + + {{#if usesAltFormula}} +
+ +
+ +
+
+ {{/if}} + + +
diff --git a/templates/sheets/rollTable/results.hbs b/templates/sheets/rollTable/results.hbs new file mode 100644 index 00000000..5e7944b0 --- /dev/null +++ b/templates/sheets/rollTable/results.hbs @@ -0,0 +1,55 @@ +
+ + + + + + + + + + + + {{#each results as |result i|}} + + + + + + + + + + + + + + {{/each}} + +
+ + {{localize "TABLE_RESULT.Details"}}{{localize "TABLE_RESULT.FIELDS.weight.label"}}{{localize "TABLE_RESULT.FIELDS.range.label"}} + +
+ {{localize + + {{> "templates/sheets/roll-table/result-details.hbs" result=result}} + + + + + + + + + + +
+
diff --git a/templates/sheets/rollTable/sheet.hbs b/templates/sheets/rollTable/sheet.hbs new file mode 100644 index 00000000..f066825a --- /dev/null +++ b/templates/sheets/rollTable/sheet.hbs @@ -0,0 +1,49 @@ +
+ {{localize +

{{document.name}}

+
+ {{#if usesAltFormula}} + + {{/if}} +

{{selectedFormula}}

+
+ +
+ +{{{descriptionHTML}}} + + + + + + + + + + + + {{#each results as |result i|}} + + + + + + + {{/each}} + +
{{localize "TABLE_RESULT.FIELDS.range.label"}}{{localize "TABLE_RESULT.Details"}}
+ {{localize + {{result.range}} + {{> "templates/sheets/roll-table/result-details.hbs" result=result}} + + + +
\ No newline at end of file diff --git a/templates/sheets/rollTable/summary.hbs b/templates/sheets/rollTable/summary.hbs new file mode 100644 index 00000000..0bcd7f9d --- /dev/null +++ b/templates/sheets/rollTable/summary.hbs @@ -0,0 +1,22 @@ +
+ {{formGroup fields.description value=source.description rootId=rootId}} +
+ {{localize "DAGGERHEART.ROLLTABLES.formula"}} + +
+ {{localize "DAGGERHEART.ROLLTABLES.FIELDS.formulaName.label"}} + {{localize "Formula Roll"}} + + + {{formInput fields.formula value=source.formula placeholder=formulaPlaceholder rootId=rootId}} + + {{#each @root.altFormula as | formula key |}} + + + + {{/each}} +
+
+ {{formGroup fields.replacement value=source.replacement rootId=rootId}} + {{formGroup fields.displayRoll value=source.displayRoll rootId=rootId}} +
diff --git a/templates/ui/chat/table-result.hbs b/templates/ui/chat/table-result.hbs new file mode 100644 index 00000000..3eedad1f --- /dev/null +++ b/templates/ui/chat/table-result.hbs @@ -0,0 +1,17 @@ +
+ {{#if flavor}}
{{flavor}}
{{/if}} + + {{#if description}} +
{{{description}}}
+ {{/if}} + {{{rollHTML}}} + + +