diff --git a/assets/_credit.txt b/assets/_credit.txt index b58b8a5..b899bcc 100644 --- a/assets/_credit.txt +++ b/assets/_credit.txt @@ -5,4 +5,7 @@ Soetarman Atmodjo: - icons/roll.svg (https://thenounproject.com/icon/dice-5195278/) : Rights Purchased. SuperNdre: - - icons/edit.svg (https://thenounproject.com/icon/edit-5208207/) : Rights Purchased \ No newline at end of file + - icons/edit.svg (https://thenounproject.com/icon/edit-5208207/) : Rights Purchased + +YANDI RS: + - icons/d8-outline (https://thenounproject.com/icon/d8-7272826/) : Rights Purchased \ No newline at end of file diff --git a/assets/icons/d8-outline.svg b/assets/icons/d8-outline.svg new file mode 100644 index 0000000..0346f4c --- /dev/null +++ b/assets/icons/d8-outline.svg @@ -0,0 +1,3 @@ + diff --git a/langs/en-ca.json b/langs/en-ca.json index 3b1513e..7a985f3 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -90,7 +90,18 @@ "long-range": "Long @RipCrypt.common.range", "current-wear": "Current @RipCrypt.common.wear", "max-wear": "Maximum @RipCrypt.common.wear", - "location-placeholder": "New Location..." + "location-placeholder": "New Location...", + "numberOfDice": "# of Dice", + "rollTarget": "Target", + "difficulty": "(DC: {dc})" + }, + "notifs": { + "error": { + "invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action." + }, + "warn": { + "cannot-go-negative": "\"{name}\" is unable to be a negative number." + } } } } diff --git a/module/Apps/DicePool.mjs b/module/Apps/DicePool.mjs new file mode 100644 index 0000000..13b0449 --- /dev/null +++ b/module/Apps/DicePool.mjs @@ -0,0 +1,158 @@ +import { filePath } from "../consts.mjs"; +import { GenericAppMixin } from "./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, + roll: this.#roll, + }, + }; + + static PARTS = { + numberOfDice: { + template: filePath(`templates/Apps/DicePool/numberOfDice.hbs`), + }, + target: { + template: filePath(`templates/Apps/DicePool/target.hbs`), + }, + buttons: { + template: filePath(`templates/Apps/DicePool/buttons.hbs`), + }, + }; + // #endregion + + // #region Instance Data + _diceCount; + _target; + + constructor({ + diceCount = 1, + target, + flavor = ``, + ...opts + } = {}) { + super(opts); + + 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`: { + this._prepareNumberOfDice(ctx); + break; + }; + case `target`: { + this._prepareTarget(ctx); + break; + }; + case `buttons`: { + break; + }; + } + + Logger.debug(`${partId} Context:`, ctx); + return ctx; + }; + + async _prepareNumberOfDice(ctx) { + ctx.numberOfDice = this._diceCount; + ctx.decrementDisabled = this._diceCount <= 0; + }; + + async _prepareTarget(ctx) { + ctx.target = this._target; + ctx.incrementDisabled = this._target >= 8; + ctx.decrementDisabled = this._target <= 1; + }; + // #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 #roll() { + const formula = `${this._diceCount}d8rc${this._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 +}; diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 8ebd20f..58aa790 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -1,7 +1,4 @@ -import { localizer } from "../utils/Localizer.mjs"; -import { Logger } from "../utils/Logger.mjs"; - -const { Roll } = foundry.dice; +import { DicePool } from "./DicePool.mjs"; /** * A mixin that takes the class from HandlebarsApplicationMixin and @@ -46,20 +43,11 @@ export function GenericAppMixin(HandlebarsApp) { /** @this {GenericRipCryptApp} */ static async rollDice(_$e, el) { const data = el.dataset; - const formula = data.formula; - Logger.debug(`Attempting to roll formula: ${formula}`); + const diceCount = parseInt(data.diceCount); + const flavor = data.flavor; - let flavor; - if (data.flavor) { - flavor = localizer(data.flavor); - } - - const roll = new Roll(formula); - await roll.evaluate(); - await roll.toMessage({ - speaker: ChatMessage.getSpeaker({ actor: this.actor }), - flavor, - }); + const dp = new DicePool({ diceCount, flavor }); + dp.render({ force: true }); }; // #endregion }; diff --git a/module/Apps/elements/RipCryptBorder.mjs b/module/Apps/elements/RipCryptBorder.mjs new file mode 100644 index 0000000..f2722c1 --- /dev/null +++ b/module/Apps/elements/RipCryptBorder.mjs @@ -0,0 +1,55 @@ +import { StyledShadowElement } from "./mixins/StyledShadowElement.mjs"; + +/** +Attributes: +*/ +export class RipCryptBorder extends StyledShadowElement(HTMLElement) { + static elementName = `rc-border`; + static formAssociated = false; + + /* Stuff for the mixin to use */ + static _stylePath = `css/components/rc-border.css`; + #container; + + _mounted = false; + async connectedCallback() { + super.connectedCallback(); + if (this._mounted) { return }; + + /* + This converts all of the double-dash prefixed properties on the element to + CSS variables so that they don't all need to be provided by doing style="" + */ + for (const attrVar of this.attributes) { + if (attrVar.name?.startsWith(`var:`)) { + const prop = attrVar.name.replace(`var:`, ``); + this.style.setProperty(`--` + prop, attrVar.value); + }; + }; + + this.#container = document.createElement(`div`); + this.#container.classList = `rc-border`; + + const titleContainer = document.createElement(`div`); + titleContainer.classList = `title`; + const titleSlot = document.createElement(`slot`); + titleSlot.innerHTML = `No Title`; + titleSlot.name = `title`; + titleContainer.appendChild(titleSlot.cloneNode(true)); + this.#container.appendChild(titleContainer.cloneNode(true)); + + const contentSlot = document.createElement(`slot`); + contentSlot.name = `content`; + this.#container.appendChild(contentSlot.cloneNode(true)); + + this._shadow.appendChild(this.#container); + + this._mounted = true; + }; + + disconnectedCallback() { + super.disconnectedCallback(); + if (!this._mounted) { return }; + this._mounted = false; + }; +}; diff --git a/module/Apps/elements/_index.mjs b/module/Apps/elements/_index.mjs index 4bde4f2..3568481 100644 --- a/module/Apps/elements/_index.mjs +++ b/module/Apps/elements/_index.mjs @@ -1,10 +1,12 @@ import { Logger } from "../../utils/Logger.mjs"; +import { RipCryptBorder } from "./RipCryptBorder.mjs"; import { RipCryptIcon } from "./Icon.mjs"; import { RipCryptSVGLoader } from "./svgLoader.mjs"; const components = [ RipCryptIcon, RipCryptSVGLoader, + RipCryptBorder, ]; export function registerCustomComponents() { diff --git a/module/api.mjs b/module/api.mjs new file mode 100644 index 0000000..97af86f --- /dev/null +++ b/module/api.mjs @@ -0,0 +1,22 @@ +import { CombinedHeroSheet } from "./Apps/ActorSheets/CombinedHeroSheet.mjs"; +import { DicePool } from "./Apps/DicePool.mjs"; +import { HeroSkillsCardV1 } from "./Apps/ActorSheets/HeroSkillsCardV1.mjs"; +import { HeroSummaryCardV1 } from "./Apps/ActorSheets/HeroSummaryCardV1.mjs"; + +const { deepFreeze } = foundry.utils; + +Object.defineProperty( + globalThis, + `ripcrypt`, + { + value: deepFreeze({ + Apps: { + DicePool, + CombinedHeroSheet, + HeroSummaryCardV1, + HeroSkillsCardV1, + }, + }), + writable: false, + }, +); diff --git a/module/main.mjs b/module/main.mjs index 58ec33d..064d9f8 100644 --- a/module/main.mjs +++ b/module/main.mjs @@ -2,3 +2,6 @@ import "./hooks/init.mjs"; import "./hooks/ready.mjs"; import "./hooks/hotReload.mjs"; + +// Global API +import "./api.mjs"; diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs new file mode 100644 index 0000000..00c0fa4 --- /dev/null +++ b/module/settings/metaSettings.mjs @@ -0,0 +1,8 @@ +export function registerMetaSettings() { + game.settings.register(`ripcrypt`, `dc`, { + scope: `world`, + type: Number, + config: false, + requiresReload: false, + }); +}; diff --git a/templates/Apps/DicePool/buttons.hbs b/templates/Apps/DicePool/buttons.hbs new file mode 100644 index 0000000..0dc1b00 --- /dev/null +++ b/templates/Apps/DicePool/buttons.hbs @@ -0,0 +1,8 @@ +
diff --git a/templates/Apps/DicePool/numberOfDice.hbs b/templates/Apps/DicePool/numberOfDice.hbs new file mode 100644 index 0000000..b3983b9 --- /dev/null +++ b/templates/Apps/DicePool/numberOfDice.hbs @@ -0,0 +1,33 @@ +