Merge pull request #64 from Eldritch-Oliver/feature/haste-roll-shortcut

Added a button to roll for hasty turns in the Actor Stats sheet
This commit is contained in:
Oliver 2025-10-08 17:55:56 -06:00 committed by GitHub
commit a6047ff009
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 169 additions and 18 deletions

View file

@ -1,7 +1,6 @@
import { CraftCardV1 } from "./CraftCardV1.mjs";
import { filePath } from "../../consts.mjs";
import { GenericAppMixin } from "../GenericApp.mjs";
import { Logger } from "../../utils/Logger.mjs";
import { SkillsCardV1 } from "./SkillsCardV1.mjs";
import { StatsCardV1 } from "./StatsCardV1.mjs";
@ -23,7 +22,9 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
window: {
resizable: false,
},
actions: {},
actions: {
...StatsCardV1.DEFAULT_OPTIONS.actions,
},
form: {
submitOnChange: true,
closeOnSubmit: false,

View file

@ -1,4 +1,5 @@
import { deleteItemFromElement, editItemFromElement } from "../utils.mjs";
import { DelveDiceHUD } from "../DelveDiceHUD.mjs";
import { filePath } from "../../consts.mjs";
import { gameTerms } from "../../gameTerms.mjs";
import { GenericAppMixin } from "../GenericApp.mjs";
@ -25,6 +26,7 @@ export class StatsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
resizable: false,
},
actions: {
rollForHaste: DelveDiceHUD.rollForHaste,
},
form: {
submitOnChange: true,

View file

@ -6,6 +6,7 @@ import { Logger } from "../utils/Logger.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
const { ContextMenu } = foundry.applications.ux;
const { Roll } = foundry.dice;
const { FatePath } = gameTerms;
const CompassRotations = {
@ -189,18 +190,7 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
/** @this {DelveDiceHUD} */
static async #tourDelta(_event, element) {
const delta = parseInt(element.dataset.delta);
const initial = game.settings.get(`ripcrypt`, `sandsOfFateInitial`);
let newSands = this._sandsOfFate + delta;
if (newSands > initial) {
Logger.info(`Cannot go to a previous Delve Tour above the initial value`);
return;
};
if (newSands === 0) {
newSands = initial;
await this.alertCrypticEvent();
};
await this.sandsOfFateDelta(delta);
switch (Math.sign(delta)) {
case -1: {
@ -212,9 +202,6 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
break;
}
};
this.#animateSandsTo(newSands);
game.settings.set(`ripcrypt`, `sandsOfFate`, newSands);
};
/** @this {DelveDiceHUD} */
@ -247,5 +234,63 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
game.togglePause(true, { broadcast: true });
};
};
/**
* Changes the current Sands of Fate by an amount provided, animating the
* @param {number} delta The amount of change
*/
async sandsOfFateDelta(delta) {
const initial = game.settings.get(`ripcrypt`, `sandsOfFateInitial`);
let newSands = this._sandsOfFate + delta;
if (newSands > initial) {
Logger.info(`Cannot increase the Sands of Fate to a value about the initial`);
return;
};
if (newSands === 0) {
newSands = initial;
await this.alertCrypticEvent();
};
this.#animateSandsTo(newSands);
game.settings.set(`ripcrypt`, `sandsOfFate`, newSands);
};
/**
* A helper method that rolls the dice required for hasty turns while delving
* and adjusts the Sands of Fate accordingly
*/
static async rollForHaste() {
const shouldUpdateSands = game.settings.get(`ripcrypt`, `allowUpdateSandsSocket`);
if (shouldUpdateSands && game.users.activeGM == null) {
ui.notifications.error(localizer(`RipCrypt.notifs.error.no-active-gm`));
return;
};
const roll = new Roll(`1d8xo=1`);
await roll.evaluate();
let delta = 0;
if (roll.dice[0].results[0].exploded) {
delta = -1;
if (roll.dice[0].results[1].result === 1) {
delta = -2;
};
};
roll.toMessage({ flavor: `Haste Check` });
// Change the Sands of Fate setting if required
if (delta === 0 || !shouldUpdateSands) { return };
if (game.user.isActiveGM) {
ui.delveDice.sandsOfFateDelta(delta);
} else {
game.socket.emit(`system.ripcrypt`, {
event: `updateSands`,
payload: { delta },
});
};
};
// #endregion
};

View file

@ -34,6 +34,7 @@ import { Logger } from "../utils/Logger.mjs";
import { registerCustomComponents } from "../Apps/components/_index.mjs";
import { registerDevSettings } from "../settings/devSettings.mjs";
import { registerMetaSettings } from "../settings/metaSettings.mjs";
import { registerSockets } from "../sockets/_index.mjs";
import { registerUserSettings } from "../settings/userSettings.mjs";
import { registerWorldSettings } from "../settings/worldSettings.mjs";
@ -127,6 +128,7 @@ Hooks.once(`init`, () => {
CONFIG.Actor.trackableAttributes.hero = HeroData.trackableAttributes;
// #endregion
registerSockets();
registerCustomComponents();
Handlebars.registerHelper(helpers);
});

View file

@ -39,4 +39,14 @@ export function registerWorldSettings() {
},
}),
});
game.settings.register(`ripcrypt`, `allowUpdateSandsSocket`, {
name: `RipCrypt.setting.allowUpdateSandsSocket.name`,
hint: `RipCrypt.setting.allowUpdateSandsSocket.hint`,
scope: `world`,
config: true,
requiresReload: false,
type: Boolean,
default: true,
});
};

27
module/sockets/_index.mjs Normal file
View file

@ -0,0 +1,27 @@
import { localizer } from "../utils/Localizer.mjs";
import { Logger } from "../utils/Logger.mjs";
import { updateSands } from "./updateSands.mjs";
const events = {
updateSands,
};
export function registerSockets() {
Logger.info(`Setting up socket listener`);
game.socket.on(`system.ripcrypt`, (data, userID) => {
const { event, payload } = data ?? {};
if (event == null || payload === undefined) {
ui.notifications.error(localizer(`RipCrypt.notifs.error.invalid-socket`));
return;
};
if (events[event] == null) {
ui.notifications.error(localizer(`RipCrypt.notifs.error.unknown-socket-event`, { event }));
return;
};
const user = game.users.get(userID);
events[event](payload, user);
});
};

View file

@ -0,0 +1,37 @@
import { clamp } from "../utils/clamp.mjs";
import { localizer } from "../utils/Localizer.mjs";
export function updateSands(payload) {
if (!game.user.isActiveGM) { return };
if (!game.settings.get(game.system.id, `allowUpdateSandsSocket`)) { return };
// Assert payload validity
const { value, delta } = payload;
if (value == null && delta == null) {
ui.notifications.error(localizer(
`RipCrypt.notifs.error.malformed-socket-payload`,
{
event: `updateSands`,
details: `Either value or delta must be provided`,
},
));
return;
};
// Take action
if (value != null) {
const initial = game.settings.get(game.system.id, `sandsOfFateInitial`);
let sands = clamp(0, value, initial);
if (sands === 0) {
ui.delveDice.alertCrypticEvent();
sands = initial;
};
game.settings.set(
game.system.id,
`sandsOfFate`,
sands,
);
} else if (delta != null) {
ui.delveDice.sandsOfFateDelta(delta);
};
};

3
module/utils/clamp.mjs Normal file
View file

@ -0,0 +1,3 @@
export function clamp(min, ideal, max) {
return Math.max(min, Math.min(ideal, max));
};