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:
commit
a6047ff009
12 changed files with 169 additions and 18 deletions
|
|
@ -155,6 +155,10 @@
|
||||||
"both": "Notification and Pause Game",
|
"both": "Notification and Pause Game",
|
||||||
"nothing": "Do Nothing"
|
"nothing": "Do Nothing"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"allowUpdateSandsSocket": {
|
||||||
|
"name": "Player Haste Updates the Sands of Fate",
|
||||||
|
"hint": "This setting determines if when a player makes a haste check that the result will automatically be applied to the global Sands of Fate. Disabling this is good if you want to let players roll without needing to worry about automation messing anything up while they spam rolls."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Apps": {
|
"Apps": {
|
||||||
|
|
@ -193,7 +197,11 @@
|
||||||
"error": {
|
"error": {
|
||||||
"cannot-equip": "Cannot equip the {itemType}, see console for more details.",
|
"cannot-equip": "Cannot equip the {itemType}, see console for more details.",
|
||||||
"invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action.",
|
"invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action.",
|
||||||
"at-favourite-limit": "Cannot favourite more than three items, unfavourite one to make space."
|
"at-favourite-limit": "Cannot favourite more than three items, unfavourite one to make space.",
|
||||||
|
"invalid-socket": "Invalid socket data received, this means a module or system bug is present.",
|
||||||
|
"unknown-socket-event": "An unknown socket event was received: {event}",
|
||||||
|
"no-active-gm": "No active @USER.GM is logged in, you must wait for a @USER.GM to be active before you can do that.",
|
||||||
|
"malformed-socket-payload": "Socket event \"{event}\" received with malformed payload. Details: {details}"
|
||||||
},
|
},
|
||||||
"warn": {
|
"warn": {
|
||||||
"cannot-go-negative": "\"{name}\" is unable to be a negative number."
|
"cannot-go-negative": "\"{name}\" is unable to be a negative number."
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { CraftCardV1 } from "./CraftCardV1.mjs";
|
import { CraftCardV1 } from "./CraftCardV1.mjs";
|
||||||
import { filePath } from "../../consts.mjs";
|
import { filePath } from "../../consts.mjs";
|
||||||
import { GenericAppMixin } from "../GenericApp.mjs";
|
import { GenericAppMixin } from "../GenericApp.mjs";
|
||||||
import { Logger } from "../../utils/Logger.mjs";
|
|
||||||
import { SkillsCardV1 } from "./SkillsCardV1.mjs";
|
import { SkillsCardV1 } from "./SkillsCardV1.mjs";
|
||||||
import { StatsCardV1 } from "./StatsCardV1.mjs";
|
import { StatsCardV1 } from "./StatsCardV1.mjs";
|
||||||
|
|
||||||
|
|
@ -23,7 +22,9 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
|
||||||
window: {
|
window: {
|
||||||
resizable: false,
|
resizable: false,
|
||||||
},
|
},
|
||||||
actions: {},
|
actions: {
|
||||||
|
...StatsCardV1.DEFAULT_OPTIONS.actions,
|
||||||
|
},
|
||||||
form: {
|
form: {
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
closeOnSubmit: false,
|
closeOnSubmit: false,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { deleteItemFromElement, editItemFromElement } from "../utils.mjs";
|
import { deleteItemFromElement, editItemFromElement } from "../utils.mjs";
|
||||||
|
import { DelveDiceHUD } from "../DelveDiceHUD.mjs";
|
||||||
import { filePath } from "../../consts.mjs";
|
import { filePath } from "../../consts.mjs";
|
||||||
import { gameTerms } from "../../gameTerms.mjs";
|
import { gameTerms } from "../../gameTerms.mjs";
|
||||||
import { GenericAppMixin } from "../GenericApp.mjs";
|
import { GenericAppMixin } from "../GenericApp.mjs";
|
||||||
|
|
@ -25,6 +26,7 @@ export class StatsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
|
||||||
resizable: false,
|
resizable: false,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
rollForHaste: DelveDiceHUD.rollForHaste,
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { Logger } from "../utils/Logger.mjs";
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
const { ContextMenu } = foundry.applications.ux;
|
const { ContextMenu } = foundry.applications.ux;
|
||||||
|
const { Roll } = foundry.dice;
|
||||||
const { FatePath } = gameTerms;
|
const { FatePath } = gameTerms;
|
||||||
|
|
||||||
const CompassRotations = {
|
const CompassRotations = {
|
||||||
|
|
@ -189,18 +190,7 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
/** @this {DelveDiceHUD} */
|
/** @this {DelveDiceHUD} */
|
||||||
static async #tourDelta(_event, element) {
|
static async #tourDelta(_event, element) {
|
||||||
const delta = parseInt(element.dataset.delta);
|
const delta = parseInt(element.dataset.delta);
|
||||||
const initial = game.settings.get(`ripcrypt`, `sandsOfFateInitial`);
|
await this.sandsOfFateDelta(delta);
|
||||||
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();
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (Math.sign(delta)) {
|
switch (Math.sign(delta)) {
|
||||||
case -1: {
|
case -1: {
|
||||||
|
|
@ -212,9 +202,6 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.#animateSandsTo(newSands);
|
|
||||||
game.settings.set(`ripcrypt`, `sandsOfFate`, newSands);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @this {DelveDiceHUD} */
|
/** @this {DelveDiceHUD} */
|
||||||
|
|
@ -247,5 +234,63 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
game.togglePause(true, { broadcast: true });
|
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
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import { Logger } from "../utils/Logger.mjs";
|
||||||
import { registerCustomComponents } from "../Apps/components/_index.mjs";
|
import { registerCustomComponents } from "../Apps/components/_index.mjs";
|
||||||
import { registerDevSettings } from "../settings/devSettings.mjs";
|
import { registerDevSettings } from "../settings/devSettings.mjs";
|
||||||
import { registerMetaSettings } from "../settings/metaSettings.mjs";
|
import { registerMetaSettings } from "../settings/metaSettings.mjs";
|
||||||
|
import { registerSockets } from "../sockets/_index.mjs";
|
||||||
import { registerUserSettings } from "../settings/userSettings.mjs";
|
import { registerUserSettings } from "../settings/userSettings.mjs";
|
||||||
import { registerWorldSettings } from "../settings/worldSettings.mjs";
|
import { registerWorldSettings } from "../settings/worldSettings.mjs";
|
||||||
|
|
||||||
|
|
@ -127,6 +128,7 @@ Hooks.once(`init`, () => {
|
||||||
CONFIG.Actor.trackableAttributes.hero = HeroData.trackableAttributes;
|
CONFIG.Actor.trackableAttributes.hero = HeroData.trackableAttributes;
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
registerSockets();
|
||||||
registerCustomComponents();
|
registerCustomComponents();
|
||||||
Handlebars.registerHelper(helpers);
|
Handlebars.registerHelper(helpers);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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
27
module/sockets/_index.mjs
Normal 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);
|
||||||
|
});
|
||||||
|
};
|
||||||
37
module/sockets/updateSands.mjs
Normal file
37
module/sockets/updateSands.mjs
Normal 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
3
module/utils/clamp.mjs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function clamp(min, ideal, max) {
|
||||||
|
return Math.max(min, Math.min(ideal, max));
|
||||||
|
};
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
"download": "#{DOWNLOAD}#",
|
"download": "#{DOWNLOAD}#",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"bugs": "",
|
"bugs": "",
|
||||||
|
"socket": true,
|
||||||
"flags": {
|
"flags": {
|
||||||
"hotReload": {
|
"hotReload": {
|
||||||
"extensions": ["css", "hbs", "json", "mjs", "svg"],
|
"extensions": ["css", "hbs", "json", "mjs", "svg"],
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,15 @@
|
||||||
class="hero_name row-alt"
|
class="hero_name row-alt"
|
||||||
value="{{actor.name}}"
|
value="{{actor.name}}"
|
||||||
name="name"
|
name="name"
|
||||||
|
autocomplete="off"
|
||||||
>
|
>
|
||||||
|
<div class="action-row">
|
||||||
|
<button
|
||||||
|
data-action="rollForHaste"
|
||||||
|
>
|
||||||
|
Haste Check
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{!-- * Armour --}}
|
{{!-- * Armour --}}
|
||||||
<div class="armour">
|
<div class="armour">
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,13 @@
|
||||||
margin-left: calc(var(--col-gap) * -1);
|
margin-left: calc(var(--col-gap) * -1);
|
||||||
padding-left: var(--col-gap);
|
padding-left: var(--col-gap);
|
||||||
}
|
}
|
||||||
|
.action-row {
|
||||||
|
grid-column: span 3;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-bottom: 2px dashed var(--accent-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.glory-label {
|
.glory-label {
|
||||||
grid-column: 2 / span 1;
|
grid-column: 2 / span 1;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue