diff --git a/src/lang/en.yml b/src/lang/en.yml
index dba4e56..50e0303 100644
--- a/src/lang/en.yml
+++ b/src/lang/en.yml
@@ -98,6 +98,7 @@ GRIMWILD:
SheetLabels:
Actor: Grimwild Actor Sheet
Item: Grimwild Item Sheet
+ RollTable: Grimwild Crucible Sheet
Actor:
Plural:
character: Characters
diff --git a/src/module/controls/suspense.mjs b/src/module/controls/suspense.mjs
index 88d4c71..abac327 100644
--- a/src/module/controls/suspense.mjs
+++ b/src/module/controls/suspense.mjs
@@ -165,7 +165,7 @@ class SuspenseTracker {
const quickPoolHtml = isGM || quickPoolsVisibleToPlayers ? getScenePools() : "";
susControl.innerHTML = `${susControlInnerHTML}${quickPoolHtml}`;
const susElement = document.querySelector("#sus-control");
- const poolElement = document.querySelector('.quick-pool-inner');
+ const poolElement = document.querySelector(".quick-pool-inner");
if (isGM && susElement) {
susElement.querySelector("#js-sus-up").onclick = () => setSuspense(getSuspense() + 1);
diff --git a/src/module/data/actor-character.mjs b/src/module/data/actor-character.mjs
index 8ea00c3..7ac3167 100644
--- a/src/module/data/actor-character.mjs
+++ b/src/module/data/actor-character.mjs
@@ -357,7 +357,7 @@ export default class GrimwildCharacter extends GrimwildActorBase {
// Update the active turn.
const combatantTurn = combat.turns.findIndex((c) => c.id === combatant.id);
if (combatantTurn !== undefined) {
- combat.update({'turn': combatantTurn});
+ combat.update({ turn: combatantTurn });
}
}
}
diff --git a/src/module/data/item-arcana.mjs b/src/module/data/item-arcana.mjs
index 9d714c0..294c215 100644
--- a/src/module/data/item-arcana.mjs
+++ b/src/module/data/item-arcana.mjs
@@ -19,7 +19,7 @@ export default class GrimwildArcana extends GrimwildItemBase {
// Arcana type.
schema.tier = new fields.StringField({
- initial: 'minor',
+ initial: "minor"
});
// Arbitrary notes.
diff --git a/src/module/data/item-talent.mjs b/src/module/data/item-talent.mjs
index cef854e..6837765 100644
--- a/src/module/data/item-talent.mjs
+++ b/src/module/data/item-talent.mjs
@@ -51,9 +51,23 @@ export default class GrimwildTalent extends GrimwildItemBase {
})
}));
+ // @TODO refactor this to use roll tables.
+ // Crucible.
+ // schema.crucible = new CrucibleTableField();
+
return schema;
}
+ // async rollCrucible(options = {}) {
+ // const result = new grimwild.rollCrucible('{2d6}', {}, {crucible: this.crucible});
+ // await result.roll();
+ // if (options.toMessage) {
+ // result.toMessage();
+ // }
+
+ // return result;
+ // }
+
/**
* Migrate source data from prior format to the new specification.
*
diff --git a/src/module/dice/_module.mjs b/src/module/dice/_module.mjs
index 7c8afbb..a1423e6 100644
--- a/src/module/dice/_module.mjs
+++ b/src/module/dice/_module.mjs
@@ -1,2 +1,3 @@
export { default as GrimwildRoll } from "./rolls.mjs";
+export { default as GrimwildCrucibleRoll } from "./crucible-rolls.mjs";
export { default as GrimwildDiePoolRoll } from "./die-pools.mjs";
diff --git a/src/module/dice/crucible-rolls.mjs b/src/module/dice/crucible-rolls.mjs
new file mode 100644
index 0000000..c5f0144
--- /dev/null
+++ b/src/module/dice/crucible-rolls.mjs
@@ -0,0 +1,39 @@
+// @TODO make this actually work.
+export default class GrimwildCrucibleRoll extends Roll {
+ static CHAT_TEMPLATE = "systems/grimwild/templates/chat/roll-crucible.hbs";
+
+ constructor(formula, data, options) {
+ super(formula, data, options);
+ if (game.dice3d && game.settings.get("grimwild", "diceSoNiceOverride")) {
+ if (!this.options.appearance) this.options.appearance = {};
+ this.options.appearance.system = "grimwild";
+ this.options.appearance.colorset = "grimwild-dark";
+ }
+
+ if (!this.options.crucible) {
+ throw new Error("A crucible object must be supplied in options.crucible for this roll.");
+ }
+ }
+
+ async render({ flavor, template=this.constructor.CHAT_TEMPLATE, isPrivate=false }={}) {
+ if (!this._evaluated) await this.evaluate();
+ const dice = this.dice[0].results;
+
+ const chatData = {
+ formula: isPrivate ? "???" : this._formula,
+ flavor: isPrivate ? null : flavor ?? this.options.flavor,
+ user: game.user.id,
+ tooltip: isPrivate ? "" : await this.getTooltip(),
+ stat: this.options.stat,
+ dice: dice,
+ name: this.options.crucible?.name ?? "Crucible",
+ crucible: {
+ 0: this.options.crucible.table[dice[0].result][dice[1].result],
+ 1: this.options.crucible.table[dice[1].result][dice[0].result]
+ },
+ isPrivate: isPrivate
+ };
+
+ return foundry.applications.handlebars.renderTemplate(template, chatData);
+ }
+}
diff --git a/src/module/dice/rolls.mjs b/src/module/dice/rolls.mjs
index 0a91f1f..bda0f5c 100644
--- a/src/module/dice/rolls.mjs
+++ b/src/module/dice/rolls.mjs
@@ -67,7 +67,7 @@ export default class GrimwildRoll extends Roll {
chatData.result = successToResult(chatData.success);
chatData.rawResult = successToResult(chatData.rawSuccess);
chatData.isCut = chatData.success !== chatData.rawSuccess;
- chatData.isFail = ['disaster', 'grim', 'messy'].includes(chatData.result);
+ chatData.isFail = ["disaster", "grim", "messy"].includes(chatData.result);
// Separate assist dice from other dice
if (this.options?.assists) {
diff --git a/src/module/documents/chat-message.mjs b/src/module/documents/chat-message.mjs
index 8479ffa..8b7f346 100644
--- a/src/module/documents/chat-message.mjs
+++ b/src/module/documents/chat-message.mjs
@@ -1,5 +1,3 @@
-import { GrimwildActor } from "./actor.mjs";
-
export class GrimwildChatMessage extends ChatMessage {
/** @inheritDoc */
async renderHTML(...args) {
@@ -88,7 +86,7 @@ export class GrimwildChatMessage extends ChatMessage {
return;
}
}
- else if (['applyMark', 'applyHarm'].includes(action)) {
+ else if (["applyMark", "applyHarm"].includes(action)) {
if (damageTaken) {
element.setAttribute("disabled", true);
return;
@@ -107,7 +105,7 @@ export class GrimwildChatMessage extends ChatMessage {
return {
updateSpark: this._updateSpark,
applyMark: this._applyHarm,
- applyHarm: this._applyHarm,
+ applyHarm: this._applyHarm
};
}
@@ -199,25 +197,23 @@ export class GrimwildChatMessage extends ChatMessage {
let harmUpdate = {};
// Handle marks.
if (stat) {
- if (['bra', 'agi', 'wit', 'pre'].includes(stat)) {
+ if (["bra", "agi", "wit", "pre"].includes(stat)) {
const isMarked = actor.system.stats[stat].marked;
if (!isMarked) {
update[`system.stats.${stat}.marked`] = true;
}
+ else if (["bra", "agi"].includes(stat)) {
+ harmUpdate = this.calculateHarm(actor, "bloodied");
+ }
else {
- if (['bra', 'agi'].includes(stat)) {
- harmUpdate = this.calculateHarm(actor, 'bloodied');
- }
- else {
- harmUpdate = this.calculateHarm(actor, 'rattled');
- }
+ harmUpdate = this.calculateHarm(actor, "rattled");
}
}
}
// Handle harm.
if (harm) {
- if (['bloodied', 'rattled'].includes(harm)) {
+ if (["bloodied", "rattled"].includes(harm)) {
harmUpdate = this.calculateHarm(actor, harm);
}
}
@@ -255,21 +251,19 @@ export class GrimwildChatMessage extends ChatMessage {
*/
calculateHarm(actor, harm) {
const harmPools = game.settings.get("grimwild", "enableHarmPools");
- const maxHarm = game.settings.get("grimwild", `max${harm === 'bloodied' ? 'Bloodied' : 'Rattled'}`) ?? 1;
+ const maxHarm = game.settings.get("grimwild", `max${harm === "bloodied" ? "Bloodied" : "Rattled"}`) ?? 1;
const update = {};
- if (!actor.system[harm == 'bloodied' ? 'isBloodied' : 'isRattled']) {
+ if (!actor.system[harm === "bloodied" ? "isBloodied" : "isRattled"]) {
update[`system.${harm}.marked`] = true;
if (harmPools) {
update[`system.${harm}.pool.diceNum`] = 1;
}
}
+ else if (!harmPools || actor.system[harm].pool.diceNum >= maxHarm) {
+ update["system.dropped"] = true;
+ }
else {
- if (!harmPools || actor.system[harm].pool.diceNum >= maxHarm) {
- update[`system.dropped`] = true;
- }
- else {
- update[`system.${harm}.pool.diceNum`] = actor.system[harm].pool.diceNum + 1;
- }
+ update[`system.${harm}.pool.diceNum`] = actor.system[harm].pool.diceNum + 1;
}
return update;
diff --git a/src/module/documents/roll-table.mjs b/src/module/documents/roll-table.mjs
new file mode 100644
index 0000000..25acce6
--- /dev/null
+++ b/src/module/documents/roll-table.mjs
@@ -0,0 +1,80 @@
+export class GrimwildRollTable extends foundry.documents.RollTable {
+ async _preUpdate(changes, options, user) {
+ await super._preUpdate(changes, options, user);
+
+ if (changes?.flags?.core?.sheetClass === "grimwild.GrimwildRollTableCrucibleSheet") {
+ if (this.results && this.results.size < 36) {
+ const results = Array.fromRange(36 - this.results.size, 1).map((result) => {
+ return {
+ name: "",
+ range: [result, result],
+ weight: 1,
+ _id: foundry.utils.randomID()
+ };
+ });
+ changes.formula = "1d36";
+ changes.results = results;
+ }
+ }
+ }
+
+ async roll({ roll, recursive=true, _depth=0 }={}) {
+ if (this.isCrucible()) {
+ // @todo tie this to the rollCrucible() method.
+ return super.roll({ roll, recursive, _depth });
+ }
+
+ return super.roll({ roll, recursive, _depth });
+
+ }
+
+ isCrucible() {
+ return this.getFlag("core", "sheetClass") === "grimwild.GrimwildRollTableCrucibleSheet";
+ }
+
+ getCrucibleTable() {
+ if (this.isCrucible()) {
+ const grid = {
+ 1: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" },
+ 2: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" },
+ 3: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" },
+ 4: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" },
+ 5: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" },
+ 6: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" }
+ };
+ let row = 1;
+ let col = 1;
+ let count = 0;
+ this.results.forEach((result) => {
+ if (count < 36) {
+ grid[row][col] = result.name;
+ col++;
+ if (col === 7) {
+ row++;
+ col = 1;
+ }
+ count++;
+ }
+ });
+
+ return grid;
+ }
+ }
+
+ async rollCrucible({ toMessage=true }={}) {
+ const crucibleRoll = new grimwild.rollCrucible("{2d6}", {}, {
+ crucible: {
+ name: this.name,
+ instructions: "",
+ table: this.getCrucibleTable()
+ }
+ });
+
+ await crucibleRoll.roll();
+ if (toMessage) {
+ crucibleRoll.toMessage();
+ }
+
+ return crucibleRoll;
+ }
+}
diff --git a/src/module/grimwild.mjs b/src/module/grimwild.mjs
index 103445e..f7afb4b 100644
--- a/src/module/grimwild.mjs
+++ b/src/module/grimwild.mjs
@@ -3,12 +3,14 @@ import { GrimwildActor } from "./documents/actor.mjs";
import { GrimwildItem } from "./documents/item.mjs";
import { GrimwildChatMessage } from "./documents/chat-message.mjs";
import { GrimwildCombat, GrimwildCombatTracker } from "./documents/combat.mjs";
+import { GrimwildRollTable } from "./documents/roll-table.mjs";
// Import sheet classes.
import { GrimwildActorSheet } from "./sheets/actor-sheet.mjs";
import { GrimwildActorSheetVue } from "./sheets/actor-sheet-vue.mjs";
import { GrimwildActorMonsterSheetVue } from "./sheets/actor-monster-sheet-vue.mjs";
import { GrimwildItemSheet } from "./sheets/item-sheet.mjs";
import { GrimwildItemSheetVue } from "./sheets/item-sheet-vue.mjs";
+import { GrimwildRollTableCrucibleSheet } from "./sheets/table-crucible-sheet.mjs";
// Import helper/utility classes and constants.
import { GRIMWILD } from "./helpers/config.mjs";
import * as dice from "./dice/_module.mjs";
@@ -30,7 +32,8 @@ globalThis.grimwild = {
GrimwildActor,
GrimwildItem,
GrimwildChatMessage,
- GrimwildCombat
+ GrimwildCombat,
+ GrimwildRollTable
},
applications: {
GrimwildActorSheet,
@@ -38,13 +41,15 @@ globalThis.grimwild = {
GrimwildActorMonsterSheetVue,
GrimwildItemSheet,
GrimwildItemSheetVue,
- GrimwildCombatTracker
+ GrimwildCombatTracker,
+ GrimwildRollTableCrucibleSheet
},
utils: {
rollItemMacro
},
models,
roll: dice.GrimwildRoll,
+ rollCrucible: dice.GrimwildCrucibleRoll,
diePools: dice.GrimwildDiePoolRoll
};
@@ -66,6 +71,8 @@ Hooks.once("init", function () {
CONFIG.Dice.rolls.push(dice.GrimwildRoll);
CONFIG.Dice.GrimwildDicePool = dice.GrimwildDiePoolRoll;
CONFIG.Dice.rolls.push(dice.GrimwildDiePoolRoll);
+ CONFIG.Dice.GrimwildCrucibleRoll = dice.GrimwildCrucibleRoll;
+ CONFIG.Dice.rolls.push(dice.GrimwildCrucibleRoll);
// Define custom Document and DataModel classes
CONFIG.Actor.documentClass = GrimwildActor;
@@ -91,9 +98,11 @@ Hooks.once("init", function () {
// Override combat classes.
CONFIG.Combat.documentClass = grimwild.documents.GrimwildCombat;
CONFIG.ui.combat = grimwild.applications.GrimwildCombatTracker;
-
CONFIG.Token.hudClass = GrimwildTokenHud;
+ // Override the rolltable class.
+ CONFIG.RollTable.documentClass = grimwild.documents.GrimwildRollTable;
+
// Register sheet application classes
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("grimwild", GrimwildActorMonsterSheetVue, {
@@ -116,6 +125,10 @@ Hooks.once("init", function () {
label: "Grimwild Vue Sheet",
types: ["talent", "challenge", "arcana"]
});
+ foundry.documents.collections.RollTables.registerSheet("grimwild", GrimwildRollTableCrucibleSheet, {
+ makeDefault: false,
+ label: "GRIMWILD.SheetLabels.RollTable"
+ });
// Handlebars utilities.
utils.preloadHandlebarsTemplates();
@@ -227,6 +240,42 @@ Handlebars.registerHelper("toLowerCase", function (str) {
return str.toLowerCase();
});
+/* -------------------------------------------- */
+/* Setup Hook */
+/* -------------------------------------------- */
+Hooks.once("setup", function () {
+ CONFIG.TextEditor.enrichers.push(
+ {
+ pattern: /@CRUCIBLE\[([^\]]*)\]{*([^}]*)}*/gim,
+ enricher: async (match, options) => {
+ const [fullMatch, uuid, content] = match;
+ const el = document.createElement("div");
+ el.innerHTML = `${content}`;
+
+ const rollTable = await fromUuid(uuid);
+ if (rollTable && rollTable.isCrucible()) {
+ el.innerHTML = `
+
+
+ ${rollTable.name}
+
+
+
+
+ ${rollTable.results.map((result) => `| ${result.name} |
`).join("")}
+
+
+
+ `;
+ return el;
+ }
+
+ return fullMatch;
+ }
+ }
+ );
+});
+
/* -------------------------------------------- */
/* Ready Hook */
/* -------------------------------------------- */
@@ -251,6 +300,60 @@ Hooks.once("ready", function () {
}
}
});
+
+ document.addEventListener("click", async (event) => {
+ if (event.target?.classList.contains("enriched-crucible-roll")) {
+ event.preventDefault();
+ const { uuid } = event.target.dataset;
+ if (uuid) {
+ const rollTable = await fromUuid(uuid);
+ if (rollTable.isCrucible()) {
+ rollTable.rollCrucible({ toMessage: true });
+ }
+ }
+ }
+
+ if (event.target?.classList.contains("create-crucible")) {
+ event.preventDefault();
+ const label = game.i18n.format("DOCUMENT.Create", {type: "Crucible Table"});
+ try {
+ const crucibleName = await foundry.applications.api.DialogV2.prompt({
+ window: { title: label },
+ content: `
+
+
+
+
`,
+ ok: {
+ label: label,
+ callback: (event, button, dialog) => button.form.elements.name.value
+ }
+ });
+ const defaultData = Array.fromRange(36,1).map(result => {
+ return {
+ name: "",
+ range: [result, result],
+ weight: 1,
+ type: "text"
+ };
+ });
+ RollTable.create({
+ name: crucibleName?.length > 0 ? crucibleName : "Crucible",
+ formula: "1d36",
+ results: defaultData,
+ flags: {
+ core: {
+ sheetClass: "grimwild.GrimwildRollTableCrucibleSheet"
+ }
+ }
+ });
+ }
+ catch (error) {
+ console.error(error);
+ return;
+ }
+ }
+ });
});
Hooks.once("renderHotbar", function () {
@@ -267,6 +370,14 @@ Hooks.on("renderSceneControls", (application, html, data) => {
SUSPENSE_TRACKER.render();
});
+Hooks.on("renderDocumentDirectory", (application, html, data) => {
+ if (data.documentName === "RollTable") {
+ html.querySelector(".header-actions").insertAdjacentHTML("afterbegin", `
+
+ `);
+ }
+});
+
/* -------------------------------------------- */
/* Dice So Nice */
/* -------------------------------------------- */
diff --git a/src/module/helpers/schema.mjs b/src/module/helpers/schema.mjs
index 9a8c815..2133bd6 100644
--- a/src/module/helpers/schema.mjs
+++ b/src/module/helpers/schema.mjs
@@ -1,4 +1,3 @@
-
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const requiredString = { required: true, blank: true };
@@ -22,17 +21,36 @@ export class DicePoolField extends fields.SchemaField {
export class CrucibleTableField extends fields.SchemaField {
constructor(options, context = {}) {
+
+ /* ------------------------------------------------- */
+ // Build our table structure with nested schema fields.
+ /* ------------------------------------------------- */
+ // Build the outer columns.
+ const cols = {};
+ const d66 = Array.fromRange(6, 1);
+ d66.forEach((col) => {
+ // Build the inner rows.
+ const rows = {};
+ d66.forEach((row) => {
+ // Inner string for the actual value.
+ rows[row] = new fields.StringField(requiredString);
+ });
+ // Inner schema for rows.
+ cols[col] = new fields.SchemaField(rows);
+ });
+ // Outer schema for cols.
+ const tableSchema = new fields.SchemaField(cols);
+
+ /* ------------------------------------------------- */
+
+ // Prepare the table fields for the data model.
const tableFields = {
// Table name.
name: new fields.StringField(requiredString),
// Table roll instructions.
instructions: new fields.StringField(optionalString),
- // Table results.
- table: new fields.ArrayField( // Column.
- new fields.ArrayField( // Row.
- new fields.StringField(requiredString) // Value.
- )
- )
+ // Table results. Outer schema is columns, inner schema is rows.
+ table: tableSchema
};
super(tableFields, options, context);
diff --git a/src/module/helpers/utils.js b/src/module/helpers/utils.js
index 14ca742..b89edcf 100644
--- a/src/module/helpers/utils.js
+++ b/src/module/helpers/utils.js
@@ -10,7 +10,9 @@ export async function preloadHandlebarsTemplates() {
// Actor partials
"systems/grimwild/templates/actor/parts/character-header.hbs",
"systems/grimwild/templates/actor/parts/monster-header.hbs",
- "systems/grimwild/templates/chat/roll-action.hbs"
+ "systems/grimwild/templates/chat/roll-action.hbs",
+ "systems/grimwild/templates/chat/roll-crucible.hbs",
+ "systems/grimwild/templates/chat/die-pool-action.hbs"
];
const paths = {};
diff --git a/src/module/sheets/item-sheet-vue.mjs b/src/module/sheets/item-sheet-vue.mjs
index 2db4080..00de776 100644
--- a/src/module/sheets/item-sheet-vue.mjs
+++ b/src/module/sheets/item-sheet-vue.mjs
@@ -23,7 +23,7 @@ export class GrimwildItemSheetVue extends VueRenderingMixin(GrimwildBaseVueItemS
viewPermission: DOCUMENT_OWNERSHIP_LEVELS.LIMITED,
editPermission: DOCUMENT_OWNERSHIP_LEVELS.OWNER,
position: {
- width: 478
+ width: 600
// height: 720,
},
window: {
@@ -40,7 +40,9 @@ export class GrimwildItemSheetVue extends VueRenderingMixin(GrimwildBaseVueItemS
deleteTracker: this._deleteTracker,
createArrayEntry: this._createArrayEntry,
deleteArrayEntry: this._deleteArrayEntry,
- rollPool: this._rollPool
+ rollPool: this._rollPool,
+ // @TODO restore when crucibles are added back to talents.
+ // rollCrucible: this._rollCrucible
},
// Custom property that's merged into `this.options`
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
@@ -346,8 +348,8 @@ export class GrimwildItemSheetVue extends VueRenderingMixin(GrimwildBaseVueItemS
}
/**
- * Handle rolling pools on the character sheet.
- * @todo abstract this to the actor itself.
+ * Handle rolling pools on the item sheet.
+ * @todo abstract this to the item itself.
*
* @param {PointerEvent} event The originating click event
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
@@ -411,4 +413,19 @@ export class GrimwildItemSheetVue extends VueRenderingMixin(GrimwildBaseVueItemS
}
}
}
+
+ // @TODO restore when crucibles are added back to talents.
+ // /**
+ // * Handle rolling crucibles on the item sheet.
+ // * @todo abstract this to the item itself.
+ // *
+ // * @param {PointerEvent} event The originating click event
+ // * @param {HTMLElement} target The capturing HTML element which defined a [data-action]
+ // * @returns
+ // * @private
+ // */
+ // static async _rollCrucible(event, target) {
+ // event.preventDefault();
+ // await this.document.system.rollCrucible({ toMessage: true });
+ // }
}
diff --git a/src/module/sheets/table-crucible-sheet.mjs b/src/module/sheets/table-crucible-sheet.mjs
new file mode 100644
index 0000000..28541da
--- /dev/null
+++ b/src/module/sheets/table-crucible-sheet.mjs
@@ -0,0 +1,219 @@
+const { api, sheets } = foundry.applications;
+
+export class GrimwildRollTableCrucibleSheet extends api.HandlebarsApplicationMixin(
+ sheets.RollTableSheet
+) {
+ /**
+ * The operational mode in which a newly created instance of this sheet starts
+ * @type {"edit"|"view"}
+ */
+ static #DEFAULT_MODE = "view";
+
+ /** @inheritDoc */
+ static DEFAULT_OPTIONS = {
+ classes: ["roll-table-sheet", "grimwild-crucible-sheet"],
+ window: {
+ contentClasses: ["standard-form"],
+ icon: "fa-solid fa-table-list",
+ resizable: true
+ },
+ position: { width: 720 },
+ form: {
+ closeOnSubmit: false
+ },
+ actions: {
+ drawResult: GrimwildRollTableCrucibleSheet.#onDrawResult
+ }
+ };
+
+ /** @override */
+ static PARTS = {
+ sheet: {
+ template: "systems/grimwild/templates/roll-table/crucible-view.hbs",
+ templates: ["templates/sheets/roll-table/result-details.hbs"],
+ scrollable: ["table[data-results] tbody"],
+ root: true
+ },
+ header: { template: "templates/sheets/roll-table/edit/header.hbs" },
+ tabs: { template: "templates/generic/tab-navigation.hbs" },
+ results: {
+ template: "systems/grimwild/templates/roll-table/edit/crucible-results.hbs",
+ templates: ["templates/sheets/roll-table/result-details.hbs"],
+ scrollable: ["table[data-results] tbody"]
+ },
+ summary: { template: "templates/sheets/roll-table/edit/summary.hbs" },
+ footer: { template: "templates/generic/form-footer.hbs" }
+ };
+
+ static MODE_PARTS = {
+ edit: ["header", "results", "footer"],
+ view: ["sheet", "footer"]
+ };
+
+ grid = {
+ 1: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" },
+ 2: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" },
+ 3: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" },
+ 4: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" },
+ 5: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" },
+ 6: { 1: "", 2: "", 3: "", 4: "", 5: "", 6: "" }
+ };
+
+ /** @inheritDoc */
+ _prepareSubmitData(event, form, formData, updateData) {
+ const submitData = super._prepareSubmitData(event, form, formData, updateData);
+ for (const result of submitData.results ?? []) {
+ sheets.TableResultConfig.prepareResultUpdateData(result);
+ }
+ return submitData;
+ }
+
+ /** @inheritDoc */
+ async submit(options) {
+ if (!this.isEditMode) return;
+ return super.submit(options);
+ }
+
+ /** @inheritDoc */
+ _configureRenderOptions(options) {
+ if (!this.isEditable) this.mode = "view";
+ else if (options.isFirstRender && !this.document.results.size) this.mode = "edit";
+ return super._configureRenderOptions(options);
+ }
+
+ /* -------------------------------------------- */
+
+ /** @override */
+ _configureRenderParts(options) {
+ const parts = super._configureRenderParts(options);
+ const allowedParts = this.constructor.MODE_PARTS[this.mode];
+ for (const partId in parts) {
+ if (!allowedParts.includes(partId)) delete parts[partId];
+ }
+ return parts;
+ }
+
+ _prepareTabs(group) {
+ return { tabs: {} };
+ }
+
+ /** @inheritDoc */
+ async _preparePartContext(partId, context, options) {
+ context = await super._preparePartContext(partId, context, options);
+ const { description, results, isOwner } = context.document;
+ switch (partId) {
+ case "results":
+ context.tab = context.tabs.results;
+ context.resultFields = foundry.documents.TableResult.schema.fields;
+ await this._prepareCrucibleGrid(context);
+ break;
+ // case "summary":
+ // context.tab = context.tabs.summary;
+ // context.descriptionHTML = await TextEditor.implementation.enrichHTML(description, {secrets: isOwner});
+ // context.formulaPlaceholder = `1d${results.size || 20}`;
+ // break;
+ case "sheet": // Lone view-mode part
+ context.descriptionHTML = await TextEditor.implementation.enrichHTML(description, { secrets: isOwner });
+ context.formula = context.source.formula || `1d${results.size || 20}`;
+ await this._prepareCrucibleGrid(context);
+ break;
+ case "footer":
+ context.buttons = [
+ {
+ type: "button",
+ action: "resetResults",
+ icon: "fa-solid fa-arrow-rotate-left",
+ label: "TABLE.ACTIONS.ResetResults"
+ },
+ {
+ type: "button",
+ action: "drawResult",
+ icon: "fa-solid fa-dice-d20",
+ label: "TABLE.ACTIONS.DrawResult"
+ }
+ ];
+ if (this.isEditMode) {
+ context.buttons.unshift({
+ type: "submit",
+ icon: "fa-solid fa-floppy-disk",
+ label: "TABLE.ACTIONS.Submit"
+ });
+ }
+ }
+ return context;
+ }
+
+ /**
+ * Prepare sheet data for a single TableResult.
+ * @param {TableResult} result The result from which to prepare
+ * @returns {Promise