ripcrypt/module/Apps/DicePool.mjs
2025-10-11 22:52:51 -06:00

222 lines
4.9 KiB
JavaScript

import { filePath } from "../consts.mjs";
import { GenericAppMixin } from "./mixins/GenericApp.mjs";
import { localizer } from "../utils/Localizer.mjs";
import { Logger } from "../utils/Logger.mjs";
const { Roll } = foundry.dice;
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export class DicePool extends GenericAppMixin(HandlebarsApplicationMixin(ApplicationV2)) {
// #region Options
static DEFAULT_OPTIONS = {
classes: [
`ripcrypt--DicePool`,
],
window: {
title: `Dice Pool`,
frame: true,
positioned: true,
resizable: false,
minimizable: true,
},
position: {
width: `auto`,
height: `auto`,
},
actions: {
diceCountDelta: this.#diceCountDelta,
targetDelta: this.#targetDelta,
edgeDelta: this.#edgeDelta,
dragDelta: this.#dragDelta,
roll: this.#roll,
},
};
static PARTS = {
numberOfDice: {
template: filePath(`templates/Apps/DicePool/numberOfDice.hbs`),
},
target: {
template: filePath(`templates/Apps/DicePool/target.hbs`),
},
drag: {
template: filePath(`templates/Apps/DicePool/drag.hbs`),
},
edge: {
template: filePath(`templates/Apps/DicePool/edge.hbs`),
},
buttons: {
template: filePath(`templates/Apps/DicePool/buttons.hbs`),
},
};
// #endregion
// #region Instance Data
_diceCount;
_target;
_drag;
_edge;
constructor({
diceCount = 1,
target,
drag = 0, edge = 0,
flavor = ``,
...opts
} = {}) {
super(opts);
this._drag = drag;
this._edge = edge;
this._flavor = flavor;
this._diceCount = diceCount;
this._target = target ?? game.settings.get(`ripcrypt`, `dc`) ?? 1;
};
get title() {
if (!this._flavor) {
return super.title;
}
return `${super.title}: ${this._flavor}`;
};
// #endregion
// #region Lifecycle
async _preparePartContext(partId, ctx, _opts) {
ctx = {};
switch (partId) {
case `numberOfDice`: {
await this._prepareNumberOfDice(ctx);
break;
};
case `target`: {
await this._prepareTarget(ctx);
break;
};
case `edge`: {
await this._prepareEdge(ctx);
break;
};
case `drag`: {
await this._prepareDrag(ctx);
break;
};
case `buttons`: {
break;
};
}
Logger.debug(`${partId} Context:`, ctx);
return ctx;
};
async _prepareNumberOfDice(ctx) {
ctx.numberOfDice = this._diceCount;
ctx.decrementDisabled = this._diceCount <= 1;
};
async _prepareTarget(ctx) {
ctx.target = this._target;
ctx.incrementDisabled = this._target >= 8;
ctx.decrementDisabled = this._target <= 1;
};
async _prepareEdge(ctx) {
ctx.edge = this._edge;
ctx.incrementDisabled = false;
ctx.decrementDisabled = this._edge <= 0;
};
async _prepareDrag(ctx) {
ctx.drag = this._drag;
ctx.incrementDisabled = false;
ctx.decrementDisabled = this._drag <= 0;
};
// #endregion
// #region Actions
static async #diceCountDelta(_event, element) {
const delta = parseInt(element.dataset.delta);
if (Number.isNaN(delta)) {
ui.notifications.error(
localizer(`RipCrypt.notifs.error.invalid-delta`, { name: `@RipCrypt.Apps.numberOfDice` }),
);
return;
};
let newCount = this._diceCount + delta;
if (newCount < 0) {
ui.notifications.warn(
localizer(`RipCrypt.notifs.warn.cannot-go-negative`, { name: `@RipCrypt.Apps.numberOfDice` }),
);
};
this._diceCount = Math.max(newCount, 0);
this.render({ parts: [`numberOfDice`] });
};
static async #targetDelta(_event, element) {
const delta = parseInt(element.dataset.delta);
if (Number.isNaN(delta)) {
ui.notifications.error(
localizer(`RipCrypt.notifs.error.invalid-delta`, { name: `@RipCrypt.Apps.rollTarget` }),
);
return;
};
this._target += delta;
this.render({ parts: [`target`] });
};
static async #edgeDelta(_event, element) {
const delta = parseInt(element.dataset.delta);
if (Number.isNaN(delta)) {
ui.notifications.error(
localizer(`RipCrypt.notifs.error.invalid-delta`, { name: `@RipCrypt.common.edge` }),
);
return;
};
this._edge += delta;
this.render({ parts: [`edge`] });
};
static async #dragDelta(_event, element) {
const delta = parseInt(element.dataset.delta);
if (Number.isNaN(delta)) {
ui.notifications.error(
localizer(`RipCrypt.notifs.error.invalid-delta`, { name: `@RipCrypt.common.drag` }),
);
return;
};
this._drag += delta;
this.render({ parts: [`drag`] });
};
static async #roll() {
let target = this._target;
target -= this._edge;
target += this._drag;
target = Math.max(target, 1);
const formula = `${this._diceCount}d8rc${target}`;
Logger.debug(`Attempting to roll formula: ${formula}`);
let flavor = this._flavor;
if (this._flavor) {
flavor += ` ` + localizer(`RipCrypt.Apps.difficulty`, { dc: this._target });
}
const roll = new Roll(formula);
await roll.evaluate();
await roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor,
});
this.close();
};
// #endregion
};